教程:Hibernate,JPA –第1部分

这是关于使用Hibernate和JPA的教程的第一部分。 这部分是对JPA和Hibernate的介绍。 第二部分将研究使用Spring ORM组合Spring MVC应用程序以减少创建CRUD应用程序所需的代码量。

为此,您需要熟悉Maven,JUnit,SQL和关系数据库。

依存关系

首先,我们需要几个基本的依赖关系。 本质上分为三层:

  1. 最低层是Hibernate用于连接数据库的JDBC驱动程序。 我将使用一个简单的嵌入式数据库Derby。 没有要安装或配置的服务器,因此,即使是MySQL或PostgreSQL,其设置也更容易。 它不适合生产。
  2. 中间层是Hibernate库。 我将使用3.5.6版。 这适用于Java 1.5,而不适用于4.x。
  3. 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 server
    private 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 unique
    private String name;

    // note that no setter for ID is provided, Hibernate will generate the ID for us

    public 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 {

 @BeforeClass
 public 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 exist
  ds.setCreateDatabase('create');

  ic.bind('java:comp/env/jdbc/tutorialDS', ds);
 }

 @AfterClass
 public static void tearDownClass() throws Exception {

  InitialContext ic = new InitialContext();

  ic.unbind('java:comp/env/jdbc/tutorialDS');
 }
}

最后一块是测试用例。 实体管理器提供对数据的访问。 持久操作(在这种情况下将导致单次插入)必须在事务中执行。 实际上,在提交之前,Hibernate不会做任何工作。 您可以通过在提交之前立即添加Thread.sleep来查看此信息。

@Test
    public 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 Hibernate
        System.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实体。 但是我们将要向用户添加和删除角色。

将以下字段和方法添加到用户表:

@ManyToMany
    private Set<Role> roles = new HashSet<Role>();

    public boolean addRole(Role role) {
        return roles.add(role);
    }

    public Set<Role> getRoles() {
        return roles;
    }

@ManyToMany注释告诉JPA这是一个多对多关系。 我们可以用一个新的测试用例进行测试。 该测试在一个事务中创建用户和角色,然后在第二个事务中使用合并更新用户。 合并用于更新数据库中的实体。

@Test
    public 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')

您可以如下进行测试:

@Test
 public 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值