在資料庫 (Oracle) 中有一個 table Users,這裡將使用 JUnit 建立一筆資料。
- JPA config
1 package idv.steven.invitation.database; 2 3 import javax.sql.DataSource; 4 5 import org.apache.commons.dbcp.BasicDataSource; 6 import org.springframework.context.annotation.Bean; 7 import org.springframework.context.annotation.Configuration; 8 import org.springframework.orm.jpa.JpaTransactionManager; 9 import org.springframework.orm.jpa.JpaVendorAdapter; 10 import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 11 import org.springframework.orm.jpa.vendor.Database; 12 import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 13 import org.springframework.transaction.PlatformTransactionManager; 14 import org.springframework.transaction.annotation.EnableTransactionManagement; 15 16 @Configuration 17 @EnableTransactionManagement 18 public class JpaConfig { 19 @Bean 20 public DataSource dataSource() { 21 BasicDataSource ds = new BasicDataSource(); 22 ds.setDriverClassName("oracle.jdbc.OracleDriver"); 23 ds.setUrl("jdbc:oracle:thin:@steven-nb:1521:DemoDB"); 24 ds.setUsername("steven"); 25 ds.setPassword("P@ssw0rd"); 26 ds.setInitialSize(5); 27 ds.setMaxActive(10); 28 29 return ds; 30 } 31 32 @Bean 33 public JpaVendorAdapter jpaVendorAdapter() { 34 HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); 35 36 adapter.setDatabase(Database.ORACLE); 37 adapter.setShowSql(true); 38 adapter.setGenerateDdl(false); 39 adapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect"); 40 41 return adapter; 42 } 43 44 @Bean 45 public PlatformTransactionManager jpaTransactionManager() { 46 JpaTransactionManager tm = new JpaTransactionManager(); 47 tm.setEntityManagerFactory(this.entityManagerFactory().getObject()); 48 49 return tm; 50 } 51 52 @Bean 53 public LocalContainerEntityManagerFactoryBean entityManagerFactory() { 54 LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean(); 55 emf.setDataSource(this.dataSource()); 56 emf.setPackagesToScan(new String[] {"idv.steven.invitation.database.dao", "idv.steven.invitation.database.entity" }); 57 emf.setJpaVendorAdapter(this.jpaVendorAdapter()); 58 59 return emf; 60 } 61 }
- 任何標識有 @Configuration 的類別,即為 spring 的設定類別 (取代了之前的 xml 設定檔)。
- @EnableTransactionManagement 表示,將由 spring 來管理 transaction。
- 這個類別就相當於採用 xml 設定時的 persistence.xml (參考: Hibernate + Spring + JPA (configure))。
- 第 34 行,在 spring 的 docs 中有這段說明「JpaVendorAdapter implementation for Hibernate EntityManager. Developed and tested against Hibernate 3.6, 4.2/4.3 as well as 5.x. Hibernate 4.2+ is strongly recommended for use with Spring 4.0+.」。
- 第 56 行,掃描 JPA 有關套件,將相關的類別載入。
- 系統設定
1 package idv.steven.invitation; 2 3 import org.springframework.context.annotation.ComponentScan; 4 import org.springframework.context.annotation.Configuration; 5 6 @Configuration 7 @ComponentScan(basePackages = {"idv.steven.invitation.database"}) 8 public class SystemConfig { 9 10 }
- 這是個系統設定類別,當然類別裡一般是可以使用 @Bean 載入許多類別,因為我們的例子很簡單,這裡只是用第 7 行的方式掃描指定的套件,以載入上面的 JpaConfig 類別。
- Entity
1 package idv.steven.invitation.database.entity; 2 3 import java.io.Serializable; 4 5 import javax.persistence.Column; 6 import javax.persistence.Entity; 7 import javax.persistence.Id; 8 import javax.persistence.Table; 9 import javax.persistence.UniqueConstraint; 10 11 @Entity 12 @Table( 13 name="USERS", 14 uniqueConstraints = { 15 @UniqueConstraint(columnNames = "EMAIL") 16 }) 17 public class Users implements Serializable { 18 private static final long serialVersionUID = 3965333920543479036L; 19@Id 20 private String email; 21 private String password; 22 private String phone1; 23 private String phone2; 24 private String nickname; 25 private String city; 26 private String gender; 27 private String birthday; 28 private String industry; 29 private String occupation; 30 private String description; 31 32 … getter & setter method 33 34 }
- ORM 中 entity 與實體 table 的對映,getter & setter method 我在這裡就省略不佔篇幅,第 14~16 行指出 EMail 欄位為 unique (這個 table 以 email 欄位為 primary key)。
- DAO
1 package idv.steven.invitation.database.dao; 2 3 import idv.steven.invitation.database.entity.Users; 4 5 public interface UsersDAO { 6 public int create(Users user); 7 }
- 定義 DAO 介面,這是可以省略,直接定義下面的類別也可以,這是為了設計上的彈性。
1 package idv.steven.invitation.database.dao; 2 3 import javax.persistence.EntityManager; 4 import javax.persistence.PersistenceContext; 5 6 import org.springframework.stereotype.Repository; 7 import org.springframework.transaction.annotation.Transactional; 8 9 import idv.steven.invitation.database.entity.Users; 10 11 @Repository 12 public class UsersDAOImpl implements UsersDAO { 13 @PersistenceContext 14 private EntityManager manager; 15 16 @Transactional 17 public int create(Users user) { 18 manager.persist(user); 19 return 1; 20 } 21 }
- 第 13 行,由 JpaConfig 類別裡載入 EntityManager,這裡不能用 @Inject 或 @Autowired 而要用 @PersistenceContext,因為這裡不是直接載入一個 Bean,而是由 EntityManagerFactory bean 產生一個 EntityManager。
- 第 16 行指出這個 method 的 transaction 要交由 spring 控管,所以在這個 method 一開始 spring 會產生一個 transaction,在離開 method 前根據成功或失敗決定 commit 或 rollback。
- 單元測試 JUnit
1 package idv.steven.invitation.database.dao; 2 3 import static org.junit.Assert.*; 4 5 import javax.inject.Inject; 6 7 import org.junit.After; 8 import org.junit.Before; 9 import org.junit.Test; 10 import org.junit.runner.RunWith; 11 import org.springframework.test.context.ContextConfiguration; 12 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 14 import idv.steven.invitation.SystemConfig; 15 import idv.steven.invitation.database.JpaConfig; 16 import idv.steven.invitation.database.entity.Users; 17 18 @RunWith(SpringJUnit4ClassRunner.class) 19 @ContextConfiguration(classes = { JpaConfig.class, SystemConfig.class }) 20 //@Transactional 21 public class UserDAOTest { 22 @Inject 23 private UsersDAO dao; 24 25 @Before 26 public void setUp() throws Exception { 27 } 28 29 @After 30 public void tearDown() throws Exception { 31 } 32 33 @Test 34 public void testCreate() { 35 Users user = new Users(); 36 user.setEmail("hi.steven@gmail.com"); 37 user.setNickname("Steven"); 38 user.setBirthday("19750315"); 39 user.setCity("台南市"); 40 user.setGender("M"); 41 user.setPassword("password"); 42 43 int n = dao.create(user); 44 assertEquals(1, n); 45 } 46 }
- 第 19 行,載入相關設定檔。
- 第 20 行,如果在單元測試的類別前加上 @Transactional,那麼在單元測試結束後,JUnit 會將資料 rollback。
- 第 35~41 將要加入資料庫的值設定在 Users 的物件。
- 第 43 行,呼叫 DAO 將資料 insert 到資料庫,Hibernate 會產生這樣的 sql --- insert into USERS (BIRTHDAY, CITY, DESCRIPTION, GENDER, INDUSTRY, NICKNAME, OCCUPATION, PASSWORD, PHONE1, PHONE2, EMAIL) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)。
- see also
【日本電影 - 你的名字】
這部動畫電影無疑是今年日本最夯的電影,劇中以慧星撞地球及男女主角靈魂(身體)互換為背景,述說著兩人在茫茫人海中尋找生命中註定要在一起的人,確實是相當能吸引青年男女的劇情。下圖是慧星分裂後的瞬間,以拋物線來說,那個分裂出來的小塊應該會拋向遠方,在劇中則落在男女主角所在之處,這一點引來了一些批評,當然這個是個缺失,但無損整個故事的精彩。