為此,您需要熟悉Maven,JUnit,SQL和關系數據庫。
依存關系
首先,我們需要幾個基本的依賴關系。 本質上分為三層:
- 最低層是Hibernate用于連接數據庫的JDBC驅動程序。 我將使用一個簡單的嵌入式數據庫Derby。 沒有要安裝或配置的服務器,因此,即使是MySQL或PostgreSQL,其設置也更容易。 它不適合生產。
- 中間層是Hibernate庫。 我將使用3.5.6版。 這適用于Java 1.5,而不適用于4.x。
- JPA庫。
另外,我們希望使用JUnit創建測試和Tomcat,因此我們可以將其JNDI命名用于測試。 出于我們將要提到的原因,JNDI是將服務器詳細信息包含在屬性文件中的首選系統。
<dependencies><dependency><groupId>org.apache.derby</groupId><artifactId>derby</artifactId><version>10.4.1.3</version></dependency><dependency><groupId>org.hibernate</groupId><artifactId>hibernate-entitymanager</artifactId><version>3.6.9.Final</version></dependency><dependency><groupId>org.hibernate.javax.persistence</groupId><artifactId>hibernate-jpa-2.0-api</artifactId><version>1.0.0.Final</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version><scope>test</scope></dependency><dependency><groupId>org.apache.tomcat</groupId><artifactId>catalina</artifactId><version>6.0.18</version><scope>test</scope></dependency></dependencies>
組態
JPA的關鍵配置文件是persistence.xml。 這位于META-INF目錄中。 它詳細說明了要使用的持久性驅動程序以及要連接的JNDI數據源。 還可以指定其他屬性,在這種情況下,我們將包括一些Hibernate屬性。
我在其他屬性上添加了一些注釋,以便您了解它們的用途。 您可以直接配置數據源,但是使用JNDI意味著我們可以以最小的代碼更改輕松地將代碼作為獨立的代碼運行在容器中或運行單元測試。
<?xml version='1.0' encoding='UTF-8'?>
<persistence xmlns='http://java.sun.com/xml/ns/persistence'xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'xsi:schemaLocation='http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd'version='1.0'><persistence-unit name='tutorialPU' transaction-type='RESOURCE_LOCAL'><provider>org.hibernate.ejb.HibernatePersistence</provider><!-- the JNDI data source --><non-jta-data-source>java:comp/env/jdbc/tutorialDS</non-jta-data-source><properties><!-- if this is true, hibernate will print (to stdout) the SQL it executes, so you can check it to ensure it's not doing anything crazy --><property name='hibernate.show_sql' value='true' /><property name='hibernate.format_sql' value='true' /><!-- since most database servers have slightly different versions of the SQL, Hibernate needs you to choose a dialect so it knows the subtleties of talking to that server --><property name='hibernate.dialect' value='org.hibernate.dialect.DerbyDialect' /><!-- this tell Hibernate to update the DDL when it starts, very useful for development, dangerous in production --><property name='hibernate.hbm2ddl.auto' value='update' /></properties></persistence-unit>
</persistence>
實體
JPA談論實體而不是數據庫記錄。 實體是類的實例,映射到表中的單個記錄(類映射到表)。 實體字段(應使用JavaBean命名約定)被映射到列。
注釋可用于向類添加額外的信息。 它們將類標記為實體,并允許您指定有關表和列的元信息,例如名稱,大小和約束。
在我們的例子中,我們將從最簡單的實體開始。
package tutorial;import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;@Entity
@Table(name = 'usr') // @Table is optional, but 'user' is a keyword in many SQL variants
public class User {@Id // @Id indicates that this it a unique primary key@GeneratedValue // @GeneratedValue indicates that value is automatically generated by the serverprivate Long id;@Column(length = 32, unique = true)// the optional @Column allows us makes sure that the name is limited to a suitable size and is uniqueprivate String name;// note that no setter for ID is provided, Hibernate will generate the ID for uspublic long getId() {return id;}public void setName(String name) {this.name = name;}public String getName() {return name;}
}
JPA可以在啟動時使用元信息來創建DDL。 這對開發很有幫助,因為它使您可以快速啟動并運行,而無需研究創建表所需的SQL。 要添加一列嗎? 只需添加列,編譯并運行即可。 不幸的是,您獲得的便利還增加了風險(例如,當一個表具有數百萬條記錄并且您添加了新列時,數據庫服務器會做什么)和失去控制。
這是一個折衷,一旦由Hibernate創建了實體,就可以導出DDL并更改Hibernate的配置以停止其更新DDL。
測試用例
只有兩部分,首先,我們將創建一個抽象測試用例作為所有測試的根。 這將在JNDI中注冊數據源,并且我們將使用其他測試來擴展它,以便他們訪問數據庫。
package tutorial;import org.apache.derby.jdbc.EmbeddedDataSource;
import org.apache.naming.java.javaURLContextFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;import javax.naming.Context;
import javax.naming.InitialContext;public abstract class AbstractTest {@BeforeClasspublic static void setUpClass() throws Exception {System.setProperty(Context.INITIAL_CONTEXT_FACTORY, javaURLContextFactory.class.getName());System.setProperty(Context.URL_PKG_PREFIXES, 'org.apache.naming');InitialContext ic = new InitialContext();ic.createSubcontext('java:');ic.createSubcontext('java:comp');ic.createSubcontext('java:comp/env');ic.createSubcontext('java:comp/env/jdbc');EmbeddedDataSource ds = new EmbeddedDataSource();ds.setDatabaseName('tutorialDB');// tell Derby to create the database if it does not already existds.setCreateDatabase('create');ic.bind('java:comp/env/jdbc/tutorialDS', ds);}@AfterClasspublic static void tearDownClass() throws Exception {InitialContext ic = new InitialContext();ic.unbind('java:comp/env/jdbc/tutorialDS');}
}
最后一塊是測試用例。 實體管理器提供對數據的訪問。 持久操作(在這種情況下將導致單次插入)必須在事務中執行。 實際上,在提交之前,Hibernate不會做任何工作。 您可以通過在提交之前立即添加Thread.sleep來查看此信息。
@Testpublic void testNewUser() {EntityManager entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager();entityManager.getTransaction().begin();User user = new User();user.setName(Long.toString(new Date().getTime()));entityManager.persist(user);entityManager.getTransaction().commit();// see that the ID of the user was set by HibernateSystem.out.println('user=' + user + ', user.id=' + user.getId());User foundUser = entityManager.find(User.class, user.getId());// note that foundUser is the same instance as user and is a concrete class (not a proxy)System.out.println('foundUser=' + foundUser);assertEquals(user.getName(), foundUser.getName());entityManager.close();}
異常處理
需要開始和提交很冗長。 此外,最后一個示例是不完整的,因為如果發生異常,它將錯過任何回滾。
異常處理是樣板代碼。 就像它的JDBC一樣,它也不漂亮。 這是一個例子:
@Test(expected = Exception.class)public void testNewUserWithTxn() throws Exception {EntityManager entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager();entityManager.getTransaction().begin();try {User user = new User();user.setName(Long.toString(new Date().getTime()));entityManager.persist(user);if (true) {throw new Exception();}entityManager.getTransaction().commit();} catch (Exception e) {entityManager.getTransaction().rollback();throw e;}entityManager.close();}
由于存在更好的方法,因此我暫時將其排除在外。 稍后,我們將研究JSR-330的@Inject和Spring Data的@Transactional如何減少樣板。
實體關系
由于我們正在使用關系數據庫,因此幾乎可以肯定,我們希望在實體之間創建一個關系。 我們將創建一個角色實體,并在用戶和角色之間建立多對多關系。 要創建角色實體,只需復制用戶實體,將其命名為Role并刪除@Table行。 我們不需要創建UserRole實體。 但是我們將要向用戶添加和刪除角色。
將以下字段和方法添加到用戶表:
@ManyToManyprivate Set<Role> roles = new HashSet<Role>();public boolean addRole(Role role) {return roles.add(role);}public Set<Role> getRoles() {return roles;}
@ManyToMany注釋告訴JPA這是一個多對多關系。 我們可以用一個新的測試用例進行測試。 該測試在一個事務中創建用戶和角色,然后在第二個事務中使用合并更新用戶。 合并用于更新數據庫中的實體。
@Testpublic void testNewUserAndAddRole() {EntityManager entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager();entityManager.getTransaction().begin();User user = new User();user.setName(Long.toString(new Date().getTime()));Role role = new Role();role.setName(Long.toString(new Date().getTime()));entityManager.persist(user);entityManager.persist(role);entityManager.getTransaction().commit();assertEquals(0, user.getRoles().size());entityManager.getTransaction().begin();user.addRole(role);entityManager.merge(user);entityManager.getTransaction().commit();assertEquals(1, user.getRoles().size());entityManager.close();}
查詢
JPA允許您使用與SQL非常相似的查詢語言JPQL。 查詢可以直接編寫,但是命名查詢更易于控制,維護,并具有更好的性能,因為Hibernate可以準備該語句。 使用@NamedQuery批注指定它們。 將此行添加到@Table批注之后的User類中:
@NamedQuery(name='User.findByName', query = 'select u from User u where u.name = :name')
您可以如下進行測試:
@Testpublic void testFindUser() throws Exception {EntityManager entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager();entityManager.getTransaction().begin();User user = new User();String name = Long.toString(new Date().getTime());user.setName(name);Role role = new Role();role.setName(name);user.addRole(role);entityManager.persist(role);entityManager.persist(user);entityManager.getTransaction().commit();entityManager.close();entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager();User foundUser = entityManager.createNamedQuery('User.findByName', User.class).setParameter('name', name).getSingleResult();System.out.println(foundUser);assertEquals(name, foundUser.getName());assertEquals(1, foundUser.getRoles().size());System.out.println(foundUser.getRoles().getClass());entityManager.close();}
在此示例中,我關閉并重新打開了實體管理器。 這迫使Hibernate從數據庫中請求用戶。 注意到關于輸出的任何有趣的東西嗎? 獲取角色的SQL出現在找到的用戶的toString之后。 Hibernate為角色創建了一個代理對象(在本例中為org.hibernate.collection.PersistentSet),并且僅在您首次訪問該對象時填充它。 這可能會導致違反直覺的行為,并有其自身的陷阱。
請嘗試上述測試的此變體,在我們首先查詢角色之前,我們關閉實體管理器:
@Test(expected = LazyInitializationException.class)public void testFindUser1() throws Exception {EntityManager entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager();entityManager.getTransaction().begin();User user = new User();String name = Long.toString(new Date().getTime());user.setName(name);Role role = new Role();role.setName(name);user.addRole(role);entityManager.persist(role);entityManager.persist(user);entityManager.getTransaction().commit();entityManager.close();entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager();User foundUser = entityManager.createNamedQuery('User.findByName', User.class).setParameter('name', name).getSingleResult();entityManager.close();assertEquals(1, foundUser.getRoles().size());}
LazyInitializationException將在getRoles()調用上引發。 這不是錯誤。 實體管理器關閉后,任何實體都將無法使用。
結束
這是Hibernate JPA入門和運行的基礎。 在本教程的下一部分中,我將討論驗證,并更深入地研究其他一些細節。
參考: 教程:Hibernate,JPA –來自JCG合作伙伴 Alex Collins的第1部分 ,位于Alex Collins的博客博客中。
翻譯自: https://www.javacodegeeks.com/2012/05/tutorial-hibernate-jpa-part-1.html