Google Code Prettify

2017年1月21日 星期六

JPA + Spring: 資料庫連線設定 (JNDI@Tomcat)

Tomcat 應該是 Java 世界使用最廣的 AP server? 即使正式營運主機上安裝的是商業的 AP server (IBM WebSphere、BEA WebLogic ...),程式員在開發程式時,也多半是在本機安裝 Tomcat 用來測試,這裡整理一下兩種建立資料庫連線的方式,一個是使用 Tomcat JNDI 的方式,另一個是使用 apache commons dbcp2,用這兩個方式都可以有 connection pool,在大部份的情況下,在正式營運環境會採用 JNDI,在本機開發則可能採用 dbcp2。
  • JDBC driver
將資料庫的 JDBC driver 放到 $Tomcat$/lib 目錄下,我使用的資料庫是 oracle,所以我將 ojdbc7.jar 放到 $Tomcat$/lib 目錄下。
  • 設定 server.xml


在 eclipse 中加入 Server 後,可以選擇要將那些專案加入 server 中,這裡選擇一個命名為 Party 的專案,按【Finish】後,在 server.xml 中就會產生如下內容:
<Context docBase="Party" path="/Party" reloadable="true" source="org.eclipse.jst.jee.server:Party" />
接著在裡面加入我們要的 JNDI 設定,如下:
<Context docBase="Party" path="/Party" reloadable="true" source="org.eclipse.jst.jee.server:Party">
    <Resource auth="Container" driverClassName="oracle.jdbc.OracleDriver" maxIdle="10" maxTotal="20" maxWaitMillis="-1" name="jdbc/DemoDB" password="passw0rd" type="javax.sql.DataSource" url="jdbc:oracle:thin:@192.168.0.102:1521:DemoDB" username="steven"/>
</Context>
這樣設定完,啟動 Tomcat,Tomcat 就會自動建立起資料庫的連線,接下要看看程式要怎麼使用。
  • web.xml
<resource-ref>
     <description>oracleDB</description>
     <res-ref-name>jdbc/DemoDB</res-ref-name>
     <res-type>javax.sql.DataSource</res-type>
     <res-auth>Container</res-auth>
</resource-ref>
在專案的 web.xml 中,加入如上內容,裡面的 res-ref-name 指出了要使用的 JNDI 資源名稱,這裡當然就是上一步驟設定好的資料庫連線。
  • WebInitializer
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MvcConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}
在 spring MVC 的專案中,會以一個繼承 AbstractAnnotationConfigDispatcherServletInitializer 的類別為起始入口,就像一般在寫 application 會以 main 為起始入口一樣,這裡有兩個重要設定,在 getRootConfigClasses 中指出程式啟動後,要優先執行的設定類別,在 getServletConfigClasses 中則指出處理 servlet 的類別。雖然上面的設定都只有一個類別,實際上那是陣列,也就是說可以有多個。這一篇的重點不在 spring MVC,所以後續只會說明 RootConfig。
  • 在啟始類別中建立資料庫連線
 1 @Configuration
 2 @ComponentScan(
 3         basePackages = {"idv.steven.invitation"},
 4         includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class),
 5         excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
 6     )
 7 @Log4j
 8 public class RootConfig {
 9     
10     @Bean
11     @Profile("PROD")
12     public JndiObjectFactoryBean jndiObjectFactoryBean() {
13         JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
14         jndiObjectFB.setJndiName("jdbc/DemoDB");
15         jndiObjectFB.setResourceRef(true);
16         jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
17         
18         return jndiObjectFB;
19     }
20     
21     @Bean
22     @Profile("TEST")
23     public DataSource dataSource() {
24         BasicDataSource ds = new BasicDataSource();
25         ds.setDriverClassName("oracle.jdbc.OracleDriver");
26         ds.setUrl("jdbc:oracle:thin:@192.168.0.102:1521:DemoDB");
27         ds.setUsername("steven");
28         ds.setPassword("passw0rd");
29         ds.setInitialSize(5);
30         
31         return ds;
32     }
33 }
  1. 如文章一開頭說的,在正式營運環境和開發環境有可能使用不同的資料庫連線方式,這裡就是這樣,建立了兩個不同的連線方式,一個是使用 JndiObjectFactoryBean,另一個使用 DataSource。
  2. 第 14 行設定要使用那個 JNDI 資源,當第 15 行的 setResourceRef 設定為 true 時,就寫成如上面的方式,如果設定為 false,就得寫成 java:comp/env/jdbc/DemoDB。
  3. 第 11、22行指出這兩個 bean 會在什麼環境裡啟動,至於在什麼環境要怎麼判斷? 這是在 web.xml 中設定的,如下,裡面設定了 spring.profiles.active 的值為 PROD,這表示程式啟動時,只有 JndiObjectFactoryBean 會被建立,DataSource 不會被建立,反之亦然。
<context-param>
      <param-name>spring.profiles.active</param-name>
      <param-value>PROD</param-value>
</context-param>
  • JPA config
 1 @Configuration
 2 @EnableTransactionManagement
 3 @EnableJpaRepositories (
 4     basePackages = { "idv.steven.invitation.database.dao" },
 5     entityManagerFactoryRef = "entityManagerFactory",
 6     transactionManagerRef = "jpaTransactionManager"
 7 )
 8 public class JpaConfig {
 9     @Autowired(required=false)
10     private DataSource dataSource;
11     
12     @Autowired(required=false)
13     private JndiObjectFactoryBean jndiObjectFB;
14     
15     @Bean
16     public JpaVendorAdapter jpaVendorAdapter() {
17         HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
18         
19         adapter.setDatabase(Database.ORACLE);
20         adapter.setShowSql(true);
21         adapter.setGenerateDdl(false);
22         adapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect");
23         
24         return adapter;
25     }
26     
27     @Bean
28     public PlatformTransactionManager jpaTransactionManager() {
29         JpaTransactionManager tm = new JpaTransactionManager();
30         tm.setEntityManagerFactory(this.entityManagerFactory().getObject());
31         
32         return tm;
33     }
34         
35     @Bean
36     public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
37         LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
38         if (dataSource == null)
39             emf.setDataSource((DataSource) jndiObjectFB.getObject());
40         else
41             emf.setDataSource(dataSource);
42         emf.setPackagesToScan(new String[] { "idv.steven.invitation.database.entity" });
43         emf.setJpaVendorAdapter(this.jpaVendorAdapter());
44         
45         return emf;
46     }
47 }
這個類別在「JPA + Hibernate + Spring + JUnit (annotation)」中已有說明,這裡只針對粗體的部份補充說明,第 9、12 行一定要有 required=false 的設定,因為兩個只會有一個被建立,如果沒有設定,預設是 required=true,那麼一定會有一個因為在 RootConfig 中沒有建立,在這裡無法被注入。第 38~41 行在設定 DataSource 時,當然要判斷一下現在那個有建立就用那一個。到這裡就搞定了 ...




Forza Gesù -- 一個小女孩成長的故事】
義大利教會合唱團的這首歌在網路上被廣傳,但是最受歡迎的是影片中影片那個版本,是主唱 2010 年 4 歲第一次領唱時的影片,到了 2016 年 10 歲時小女孩長大了,失去了 4 歲時那份混然天成的童真,就沒辦法感動那麼多人了 … 還是來看一下 2010 年的版本吧 ~



1 則留言: