JPA快速入门(一)

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

//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](https://upload-images.jianshu.io/upload_images/11387429-acc5a9d5ff1ac1d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

但是一级缓存的缓存能力是非常有限的,因为我们不会经常在一个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](https://upload-images.jianshu.io/upload_images/11387429-1c342c0586756090.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
有了对对象状态的了解之后,我们来分析面的案例中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的发送有了更深入的认识

  • 6
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值