[Reprint] JPA Quick Start (a)

JPA Quick Start (a)

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

 

Author: Zhong Xin Ling, a senior lecturer knock Ding wolf education. Original articles, please indicate the source.

Introduction to JPA

JPA is referred to as the Java Persistence API, the Chinese name of the Java Persistence API, is JDK 5.0 annotations or XML description of the object - mapping between relational tables, solid objects and run persistence to the database.

Sun introduced the new JPA ORM specification for two reasons:
First, simplify the existing Java EE and Java SE application development work;
Second, Sun hopes the integration of ORM technology, the world normalized.

JPA's mission is to provide persistent POJO standards, we can see, after several years of practice and exploration, can run independently from the container, facilitate the development and testing of the concept has gained a. Hibernate3.2 +, TopLink 10.1.3 and have provided OpenJPA implementation of JPA.

JPA's overall thinking and existing Hibernate, TopLink, JDO and other ORM frameworks broadly consistent. In general, JPA includes the following three aspects of the technology:

ORM mapping metadata

XML annotation and JPA support JDK5.0 form two kinds of metadata, the metadata describes the mapping relationships between the objects and the table, the frame whereby the entity object persisted to the database tables;

API

For operating entity object, perform CRUD operations, the framework in the background alternative we have completed everything, developers freed from the tedious JDBC and SQL code.

Query Language

This is the persistence of a very important operation aspect, rather than object-oriented database query language for query data, to avoid the tight coupling process SQL statements.

 

 
image.png
JPA development environment to build
  • dependent jar package
    if maven item, add the following to the configuration file 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> 

If an ordinary java project, add the following jar package to the lib directory of the project

 

 
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的发送有了更深入的认识

Guess you like

Origin www.cnblogs.com/jinanxiaolaohu/p/12092630.html