[转帖]JPA快速入门(一)

JPA快速入门(一)

https://www.jianshu.com/p/8acf33afa41a

作者:钟昕灵,叩丁狼教育高级讲师。原创文章,转载请注明出处。

JPA简介

JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

Sun引入新的JPA ORM规范出于两个原因:
其一,简化现有Java EE和Java SE应用开发工作;
其二,Sun希望整合ORM技术,实现天下归一。

JPA的宗旨是为POJO提供持久化标准规范,由此可见,经过这几年的实践探索,能够脱离容器独立运行,方便开发和测试的理念已经深入人心了。Hibernate3.2+、TopLink 10.1.3以及OpenJPA都提供了JPA的实现。

JPA的总体思想和现有Hibernate、TopLink、JDO等ORM框架大体一致。总的来说,JPA包括以下3方面的技术:

ORM映射元数据

JPA支持XML和JDK5.0注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中;

API

用来操作实体对象,执行CRUD操作,框架在后台替代我们完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来。

查询语言

这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。

 
image.png
JPA开发环境搭建
  • jar包的依赖
    如果是maven项目,将下面的配置添加到pom.xml文件中
<build>
    <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <target>1.8</target> <source>1.8</source> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>4.3.5.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>4.3.5.Final</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.6</version> </dependency> </dependencies> 

如果是普通的java项目,将下面的jar包添加到项目的lib目录中

 
image.png
  • persistence.xml文件
    如果是maven项目,在src/main/resources下创建META-INF文件夹,将persistence.xml文件放在该目录下
    如果是普通的java项目,在src下创建META-INF文件夹,将persistence.xml文件夹放在该目录下

在persistence.xml文件中做如下配置

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0"> <!-- JPA根据下面的配置信息创建EntityManagerFactory,一个项目中可以配置多个持久单元 name:为当前持久单元命名,可以通过该名称指定加载对应的配置信息 --> <persistence-unit name="myPersistence"> <!--指定扫描贴Entity实体类所在的jar包--> <properties> <!--数据库的方言,告诉JPA当前应用使用的数据库--> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/> <!--jpa的相关的配置信息--> <property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/> <property name="javax.persistence.jdbc.user" value="root"/> <property name="javax.persistence.jdbc.password" value="admin"/> <!--是否在控制台打印执行的sql语句--> <property name="hibernate.show_sql" value="true"/> </properties> </persistence-unit> </persistence> 

到此,开发JPA应用的环境就搭建完成,接下来,在此基础上来完成基本的CRUD操作吧

基于JPA的CRUD
  • 实体类及映射配置
//getter/setter和toString方法
@Getter@Setter@ToString

//JPA会扫描到贴了Entity注解的类,将其作为需要持久化的类 @Entity //根据需求,对类和表做相关映射(如:表名) @Table(name="user") public class User { //标识该字段为主键列对应的字段 @Id //指定主键的生成策略 @GeneratedValue(strategy = GenerationType.AUTO) //为当前字段和对应的列做映射(如:列名,列的长度等) @Column(name = "id") private Long id; @Column(name = "name",length = 20) private String name; @Column(name = "sn",nullable = false) private String sn; //对日期类型做映射 @Temporal(TemporalType.DATE) private Date hiredate; } 
  • EntityManagerFactory和EntityManager对象的创建
  1. EntityManagerFactory:JPA通过加载META-INF/persistence.xml文件中配置的persistence-unit创建EntityManagerFactory对象,该对象相当于一个连接池对象,用来创建EntityManager,是线程安全的,多线程可以共用同一个EntityManagerFactory,创建该对象需要消耗较多的资源,所以通常一个项目只需要创建一个EntityManagerFactory对象
  2. EntityManager:相当于一个连接对象,该对象线程不安全,所以,每次对象数据库的访问应该创建一个新的EntityManager对象
public class JPAUtil {

    private static EntityManagerFactory emf; private JPAUtil() {} static { //加载persistence.xml文件中的persistence-util中的配置信息创建EntityManagerFactory对象 emf = Persistence.createEntityManagerFactory("myPersistence"); } //使用EntityManager创建EntityManager对象 public static EntityManager getEntityManager() { return emf.createEntityManager(); } } 
  • 保存操作
@Test
public void testSave() throws Exception { //封装需要持久化的数据 User u = new User(); u.setName("Neld"); u.setSn("sn"); u.setHiredate(new Date()); EntityManager em = JPAUtil.getEntityManager(); //开启事务 em.getTransaction().begin(); //执行保存 em.persist(u); //提交事务 em.getTransaction().commit(); //释放资源 em.close(); } 
  • 删除操作
@Test
public void testDelete() throws Exception { EntityManager em = JPAUtil.getEntityManager(); em.getTransaction().begin(); User u = em.getReference(User.class, 1L); //执行删除,将持久化状态的对象从数据库中删除 em.remove(u); em.getTransaction().commit(); em.close(); } 
  • 修改操作
@Test
public void testUpdate() throws Exception { EntityManager em = JPAUtil.getEntityManager(); em.getTransaction().begin(); User u = em.find(User.class, 1L); u.setName("xxxx"); em.merge(u); em.getTransaction().commit(); em.close(); } 
  • 查询操作
@Test
public void testGet() throws Exception { EntityManager em = JPAUtil.getEntityManager(); //查询指定类型和OID的用户信息 User u = em.find(User.class, 1L); em.close(); System.out.println(u); } 
  • CRUD小结
  1. persistence.xml文件的配置
    配置连接数据库的基本信息
    JPA的基本行为配置

  2. 实体类的基本映射
    @Entity:标注该类为持久化类
    JPA扫描到类上的注解,会将当前类作为持久化类
    @Table:配置当前类和表的相关映射
    下面的注解可以贴在字段或者是get方法上,
    如果选定了一个位置,那么所有的属性相关的注解都应该贴在这个位置,意思是说,不能一部分在字段上,一部分在get方法上
    @Id:主键属性的映射---和表中的主键映射
    @GeneratedValue:主键生成策略(指定生成主键的方式:自增长/手动设置)
    @Column:配置当前属性和列的映射
    @Temporal:对日期类型的属性映射(Date/DateTime/TimeStemp)

  3. 完成CRUD的步骤
    加载persistence.xml文件,使用指定的<persistence-unit>配置创建EntityManagerFactory对象,相当于根据配置信息创建一个连接池对象
    创建EntityManager对象,相当于获取到一个连接对象
    开启事务
    执行crud相关的方法(persist/merge/remove/find),查询所有调用Query中的getResultList方法
    Persist:保存数据
    Merge:保存或者更新,当对象有OID的时候,更新,反之,保存
    Remove:删除数据
    Find:根据主键查询数据
    Query:其他的查询需要使用该对象,传入对应的JPQL(相当于SQL),调用getResultList方法执行查询,返回对应的List集合
    提交事务
    释放资源

hbm2ddl工具的使用

在持久层应用的开发过程中,我们发现,实体类和表结构是一一对应的,所以,我们会想,是否可以让JPA根据实体类和对应的映射信息的配置,为我们自动的生成对应的表结构呢?

答案是肯定的,又因为我们现在讲的是hibernate对JPA的实现,所以我们应用hibernate中提供的hbm2ddl工具来实现,配置很简单,在persistence.xml文件中作如下配置即可

<property name="hibernate.hbm2ddl.auto" value="create"/> 

接下来,我们来解释一下每种策略的含义及使用场景

  • hibernate.hbm2ddl.auto=create
    在启动的时候先删除被管理的实体对应的表,然后再创建jpa管理的实体类对应的表
  • hibernate.hbm2ddl.auto=create-drop
    和create一致,只是在关闭系统之前会删除jpa管理的所有的表
  • hibernate.hbm2ddl.auto=update
    在启动的时候,检查实体类和表结构是否有变化,如果有,执行更新表结构相关的sql
    如果添加一个属性,JPA可以帮我们在表中添加对应的列
    如果删除一个属性,JPA不会帮我们去表中删除对应的列
    如果修改一个属性(类型),JPA不会帮我们去表中删除对应的列
  • hibernate.hbm2ddl.auto=validate
    在启动的时候,检查实体类和表结构是否有变化,如果有,启动失败,抛出异常
    Caused by: org.hibernate.HibernateException: Missing column: sn in jpa.user
选择:
  • 在开发阶段,我们通常使用create或者create-drop,可以快速的创建对应的表结构
  • 在测试阶段,不要使用create或者create-drop,因为这样会将我们辛苦录入的测试数据删除,所以,我们使用update,在实体类修改的时候,更新表结构即可
  • 在生产环境中,我们通常使用validate,这样可以在启动阶段发现表结构相关的问题,至于表结构的修改,交给我们的DBA去完成吧.

单对象映射中常用的注解

  • 对象映射相关
  1. @Entity:
    对实体类的映射,默认使用当前类的简单名称作为类名,如在使用JPQL做查询的时候,使用该名字实现数据的查询
    JPQL语句:SELECT u FROM User u;
    User:为默认使用的类名,可以通过Entity中的name属性修改
    @Entity(name=”UserInfo”):将类的名称修改为UserInfo,那么上面的JPQL中的User修改为UserInfo即可

  2. @Table:
    指定实体类映射的表的相关信息,如:表名,默认和类名一致
    @Table(name=”t_user”):将映射的表名修改为t_user

  3. persistence.xml文件中的相关元素的配置说明
    <class>:指定需要扫描的实体类
    <exclude-unlisted-classes>:设置为true的时候,表示不扫描这里没有列出来的类
    <jar-file>:指定对项目中引入的jar包中的类进行扫描

  • 属性相关:
  1. @GeneratedValue,主键生成策略
    在一张表中,主键列的信息通常需要受到程序员的特殊关照,这里我们需要探讨一下主键的生成方式(自动生成/手动设值)
    首先,我们需要在主键属性上使用@GeneratedValue注解中的strategy属性来设值主键的生成方式

    1. strategy=GenerationType.AUTO
      把主键生成策略交给JPA厂商(Persistence Provider),由它根据具体的数据库选择合适的策略,可以是Table/Sequence/Identity中的一种。假如数据库是Oracle,则选择Sequence。
      如果不做特别指定,默认是使用这种方式生成主键

    2. strategy=GenerationType.IDENTITY
      多数数据库支持IDENTITY,数据库会在新行插入时自动给ID赋值,这也叫做ID自增长列,比如MySQL中可以在创建表时声明“AUTO_INCREMENT”,该策略在Oracle数据库中不支持

    3. strategy=GenerationType.TABLE
      有时候为了不依赖于数据库的具体实现,在不同数据库之间更好的移植,可以在数据库中新建序列表来生成主键,序列表一般包含两个字段:第一个字段引用不 同的关系表,第二个字段是该关系表的最大序号。这样,只需要一张序列就可以用于多张表的主键生成。
      如果不指定表生成器,JPA厂商会使用默认的表,比如Hibernate在Oracle数据库上会默认使用表hibernate_sequence。
      这种方式虽然通用性最好,所有的关系型数据库都支持,但是由于不能充分利用具体数据库的特性,建议不要优先使用。

    4. strategy=GenerationType.SEQUENCE
      Oracle不支持ID自增长列而是使用序列的机制生成主键ID,对此,可以选用序列作为主键生成策略:
      如果不指定序列生成器的名称,则使用厂商提供的默认序列生成器,比如Hibernate默认提供的序列名称为hibernate_sequence。
      支持的数据库: Oracle、PostgreSQL、DB2
      属性映射
      @Column:
      使用该注解可以对属性和列进行相关映射

该注解可以贴在字段上,也可贴在getter方法上,但是必须是统一的,不能一部分在字段上,一部分在getter方法上

  • @Access
    在实际开发中,也可以告诉JPA只去扫描哪个位置上的@Column注解,如果没有就不在去其他地方扫描
    @Access(AccessType.PROPERTY):属性,对应着get方法
    @Access(AccessType.FIELD):字段:对应字段

  • @Column
    name:列名,通常,属性名和列名一直的时候,不需要指定,默认使用属性名作为列名
    unique:唯一性约束
    nullable:非空约束
    insertable:false,表示在生成insert语句的时候不插入这一列的值
    updatable:false,表示在生成update语句的时候不更新这一列的值
    length:指定该列的长度
    columnDefination:自定义列的类型,默认是JPA根据属性的类型自动生成
    precision:在使用decimal类型的时候指定总长度
    scale:在使用decimal类型的时候指定小数位数

  • @Temporal:
    日期类型的映射
    指定日期类型的属性对应的列的类型(date/datatime/timestamp)

  • @Transient:
    非持久化类型的映射
    JPA在做对象关系映射的时候,默认是对实体类中的所有属性进行映射的,如果有不需要映射的属性,可以使用该注解完成

  • @Lob:
    大数据类型的映射
    对象如果是String类型的,默认情况下载表中映射的是VARCHAR类型
    该注解可以对应text/blob/clob类型进行映射,如:

@Lob
private String content;

一级缓存

在EntityManager中存在一个缓存区域,称之为一级缓存

在该缓存区中,会将查询到的对象缓存到该区域中

如果在同一个EntityManager中,查询相同OID的数据,那么只需要发送一条sql

在事务提交/关闭EntityManager之后,一级缓存会清空,所以在不同的EntityManager中使用不
同的一级缓存

一级缓存也可以使用下面的方法手动清除缓存数据
detach:清除一级缓存中指定的对象
clear:清除一级缓存中的所有的缓存数据

 
image.png

但是一级缓存的缓存能力是非常有限的,因为我们不会经常在一个EntityManager中查询相同的数据
延迟加载

JPA中,根据主键查询数据可以使用下面两个方法完成:
<T> T find(Class<T> type, Object oid);
<T> T getReference(Class<T> type, Object oid);
相同点:都是根据主键查询指定类型的数据

不同点: getReference方法是在真实使用该对象的时候才会发送查询的sql语句,如

public void testGetReference() throws Exception { EntityManager em = JPAUtil.getEntityManager(); //这里不会立即发送sql查询 User u = em.getReference(User.class, 1L); System.out.println("-------------"); //在访问User对象中的属性值的时候表示真正使用该对象 System.out.println(u.getName()); em.close(); } 

执行结果:

------------- Hibernate: select user0_.id as id1_0_0_, user0_.hiredate as hiredate2_0_0_, user0_.name as name3_0_0_, user0_.sn as sn4_0_0_ from User user0_ where user0_.id=? Neld 

根据执行的打印结果可以看到,是我们在真正使用该对象的时候才会执行查询的sql,而在这之前是不会发送SQL执行数据的查询

延迟加载

getReference方法查询数据的方式我们称之为延迟加载

什么是延迟加载? 就是不会立即执行查询的sql,而是延迟到真正使用的时候再执行,上面的例子已经证明了这一点

再观察:
find方法查询到的结果,如果查询到了对应的数据,返回查询到的结果即可,反之,返回null,所以可以使用ifnull判断是否有数据
getReference方法查询到的结果,无论是否查询到了数据,结果都不会是null,所以不能使用ifnull判断是否有对应的数据
如果在表中没有对应的数据,抛出异常
javax.persistence.EntityNotFoundException: Unable to find cn.wolfcode._01_hello.User with id 2

原理:
JPA使用动态代理机制实现延迟加载,覆写该对象中的所有的getter方法,在getter方法中执行查询当前对象的sql

延迟加载需要搞懂的问题:
1.延迟加载什么时候发送SQL执行数据?
2.为什么需要在关闭EntityManager对象之前初始化延迟加载对象?
3.为什么在访问对象的get方法的时候,会去初始化当前对象(发送SQL执行查询)呢?
4.使用find方法没有查询到数据的时候,返回值是什么?使用getReference方法没有查询到数据的时候,返回值是什么?

对象状态

对象的状态是JPA中非常重要的概念,描述了实体对象从瞬时到持久、从删除到游离的状态变换。对实体的操作其实就是对象实体状态的改变, 这对于我们分析SQL的执行情况有很大的帮助。

  • 瞬时状态(Transient)
    使用new关键字创建出来的新对象,没有OID,不在一级缓存中
  • 持久状态(Persistent)
    调用持久化方法之后,将对象保存到数据库中,对象状态转化成持久状态
  • 游离状态(Detached)
    对象存在于数据库中,但是不在一级缓存中
  • 删除状态(Removed)
    事务一旦提交,对象就会被从数据库中删除,是介于持久状态和被删除之间的一个临界状态

我们可以通过下面的表格了解到各个状态的特点:

状态 是否在一级缓存 是否有OID
瞬时状态(Transient)
持久状态(Persistent)
游离状态(Detached)
删除状态(Removed)

EntityManager提供一系列的方法管理实体对象的状态,包括:

  • persist, 将新创建的或已删除的实体转变为Persistent状态,数据存入数据库。
  • remove,删除持久状态的实体
  • merge,将游离实体转变为Persistent状态,数据存入数据库。

如果使用了事务管理,则事务的commit/rollback也会改变实体的状态。
如图:

 
image.png

有了对对象状态的了解之后,我们来分析面的案例中sql的发送

@Test
public void test() throws Exception{ EntityManager em = JPAUtil.getEntityManager(); em.getTransaction().begin(); //通过find方法查询到处于持久状态的User对象 User u = em.find(User.class, 1L); u.setName("Lucy");//① em.getTransaction().commit(); em.close(); } 

执行结果:
Hibernate: select user0_.id as id1_0_0_, user0_.hiredate as hiredate2_0_0_, user0_.name as name3_0_0_, user0_.sn as sn4_0_0_ from User user0_ where user0_.id=?

Hibernate: update User set hiredate=?, name=?, sn=? where id=?

  • 分析:
    ①:在这里,我们修改了查询出来处于持久状态的User对象的name属性的值

我们并没有调用merge方法去更新User对象,为什么会发送update语句呢?

  • 原因:
    首先,将数据从数据库中查询出来后,在内存中会有两份数据,一份在EntityManager一级缓存区域,一份在EntityManager的快照区,两份数据完全一样

然后,修改User的name属性时,其实是修改的缓存区的数据

最后,在提交事务的时候,会清理一级缓存,此时会对比两份数据是否一致,如果不一致,发送对应的update语句将缓存中的脏数据(和数据库中的数据不一致)同步到数据库中

所以,在上面的例子中,我们看到执行了一条更新语句,这样相信大家就能够理解了,这也是在我们了解了对象的状态之后对SQL的发送有了更深入的认识

猜你喜欢

转载自www.cnblogs.com/jinanxiaolaohu/p/12092630.html