JPA简单使用

JPA是java中的持久层API,sun公司希望通过jpa整合orm技术,实现天下归一。JPA作为orm的规范,我是很有兴趣的把它学习了一遍。先说说jpa单独的使用,在介绍jpa与springdata的整合。

1、创建java项目

呃、这不就过了吧,也可以用eclipse建立jpa项目,学习测试嘛,都一样。

2、导入jar包

jpa作为接口,其自身是没有任何实现的,这里我们使用hibernate作为实现产品。lib/require下的所有jar文件。我使用的hibernate版本是5.2.10的。


还要导入mysql驱动,加入build path。如下图:

3、编写配置文件

主要设置jpa实现产品,继承javax.persistence.spi.PersistenceProvider接口的类,实体类的引用路径。
jpa的基本参数(数据库驱动账号密码等),实现产品的配置参数(如hibernate中的显示sql语句)。

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" ......>
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!-- jpa的实现产品 -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        
        <!-- 两个实体类 -->
        <class>cn.lkangle.entity.Student</class>
        <class>cn.lkangle.entity.Clazz</class>
        
        <properties>
            <!-- 数据库连接配置 -->
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root"/>
            
            <!-- hibernate的配置 -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL55Dialect"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
    </persistence-unit>
</persistence>

4、创建学生和班级实体类

Student.java

package cn.lkangle.entity;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

/**
 * 学生的实体类
 * @author lbxx
 * 常用注解解释:
 * @Entity 用来标示这个类是实体类
 * @Table  实体类与数据表的映射,通过name确定在表名(默认类名为表名)
 * @Id         主键注解,表示该字段为主键
 * @GeneratedValue 定义主键规则,默认AUTO
 * @Column 类属性与表字段映射注解,其中可以设置在字段名,长度等信息
 * @ManyToOne  多对一,可以设置数据加载方式等 默认加载方式是EAGER 就是使用left join
 * @OneToMany  一对多 默认加载方式是 LAZY 懒加载
 * @JoinColumn 与*对*配合使用,用来设置外键名等信息
 * @Basic  实体类中会默认为每一个属性加上这个注解,表示与数据表存在关联,
 *              没有使用Column注解的类属性会以属性名作为字段名,驼峰命名需要转为_
 * @Temporal 对于Date属性的格式化注解,有 TIME,DATE,TIMESTAMP 几个选择
 * @Transient 若存在不想与数据表映射的属性,则需要加上该注解
 */
@Entity
@Table(name = "t_student")
public class Student {
    @Id@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String  name;
    private String  tel;
    private Date    date;
    
    @JoinColumn(name = "clz_id")
    @ManyToOne(fetch = FetchType.EAGER)
    private Clazz clazz;
    ...get set省略...
}

Clazz.java

package cn.lkangle.entity;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@Table(name = "t_clazz")
public class Clazz {
    
    @Id@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String  name;
    
    @OneToMany(mappedBy = "clazz")
    private Set<Student> stus = new HashSet<>();
    
    @Temporal(TemporalType.DATE)
    private Date    date;
    ...get set省略...
}

5、测试CRUD

因为就是学习使用,这里就使用junit进行测试。

使用jpa首先要通过Persistence创建一个EntityManagerFactory实例,,然后利用它创建EntityManage实例,在通过EntityManage获取事务,开始事务进行crud操作,提交事务、、、一个基本流程就这样子。

  • 建立基本的测试流程
package cn.lkangle.test;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class JpaTest {
    private EntityManagerFactory entityManagerFactory;
    private EntityManager entityManager;
    private EntityTransaction transaction;
    
    @Before
    public void init() {
        /**
         * 通过Persistence获取EntityManagerFactory,
         * 传入参数对应配置文件中持久化单元persistence-unit的name
         * 通过EntityManagerFactory创建EntityManager
         * 获取EntityTransaction
         * 开启事务
         */
        entityManagerFactory = Persistence.createEntityManagerFactory("jpa");
        entityManager = entityManagerFactory.createEntityManager();
        transaction = entityManager.getTransaction();
        transaction.begin();
    }
    
    @After
    public void distory() {
        /**
         * 提交事务
         * 关闭entityManager
         * 关闭entityManagerFactory
         */
        transaction.commit();
        entityManager.close();
        entityManagerFactory.close();
    }
    
    @Test
    public void test() {
        
    }
}

运行test可以看见控制台输出了hibernate的键表语句,数据库中也创建了数据表

  • 增加操作
    /**
     * 添加操作
     * 在设置学生班级的时候这个班级必须是被jpa管理的持久化对象才能被设置成功
     * 需要先保存班级在保存学生
     */
    @Test
    public void testAdd() {
        Clazz clz1 = new Clazz();
        clz1.setName("计科1601");
        clz1.setDate(new Date());
        
        Student stu1 = new Student();
        stu1.setName("mary");
        stu1.setTel("18866005544");
        stu1.setDate(new Date());
        stu1.setClazz(clz1);
        
        entityManager.persist(clz1);
        entityManager.persist(stu1);
    }
  • 删除操作
    1、删除学生,直接删除

      /**
       * 被删除的对象也必须是被jpa管理的持久化对象
       */
      @Test
      public void testDeleteStu() {
          Student stu = entityManager.find(Student.class, 7);
          entityManager.remove(stu);
      }
    

    2、删除班级,因为通过外键建立了关系,直接删除会报错

    报错信息:
    ERROR: Cannot delete or update a parent row: a foreign key constraint fails (`jpa`.`t_student`, CONSTRAINT `FK1o8wvgt709w2v82g6yejbk71y` FOREIGN KEY (`clz_id`) REFERENCES `t_clazz` (`id`))
    

    解决方法:

    • 可以在一方进行级联设置
    @OneToMany(cascade = {CascadeType.REMOVE}, mappedBy = "clazz")
    private Set<Student> cls = new HashSet<>();
    

    这样在删除班级的时候会连同班级下所有的学生一起删除,这是一种很危险的级联方式,不建议使用。

    • 通过获取班级下所有的学生,先解除关系在进行删除,不需要设置级联关系
      @Test
      public void testDeleteClz() {
          Clazz clz = entityManager.find(Clazz.class, 3);
          Set<Student> stus = clz.getStus();
          stus.forEach((stu) -> stu.setClazz(null));
          entityManager.remove(clz);
      }
    

    产生的SQL语句,简化后

    Hibernate: select * from t_clazz c where c.id=?
    Hibernate: select * from t_student s where s.clz_id=?
    Hibernate: update t_student set clz_id=?, date=?, name=?, tel=? where id=?
    Hibernate: delete from t_clazz where id=?
    
  • 修改操作
    1、使用merga方法

      @Test
      public void testM() {
          Clazz clz = entityManager.find(Clazz.class, 5);
          
          Student student = new Student();
          student.setId(5);
          student.setName("lee");
          student.setDate(new Date());
          student.setTel("1885656565");
          student.setClazz(clz);
          
          Student stu = entityManager.merge(student);
      }
    

    产生的SQL语句

    Hibernate: select * from t_clazz clazz0_ where clazz0_.id=?
    Hibernate: select * from t_student student0_ where student0_.id=?
    Hibernate: update t_student set clz_id=?, date=?, name=?, tel=? where id=?
    

    2、根据持久化对象性质,直接修改。修改后jpa会在事务提交时自动检查缓存中的内容是否和数据库中一致,不一致就会更新。

      @Test
      public void testChange() {
          Clazz clz = entityManager.find(Clazz.class, 5);
          Student student = entityManager.find(Student.class, 4);
          student.setClazz(clz);
      }
    

    产生的SQL语句

    Hibernate: select * from t_clazz clazz0_ where clazz0_.id=?
    Hibernate: select * from t_student student0_ where student0_.id=?
    Hibernate: update t_student set clz_id=?, date=?, name=?, tel=? where id=?
    

    从SQL语句中看的出来,每次更新都会对所有的字段进行更新,这样的话使用第一种方式就要求我们要把对象属性都进行设置,不然就会出现null值。

  • 查询操作
    到了最复杂的查询了,其实这一块我并不熟悉,因为实际使用的时候都是和springdata集成的,他提供的各种查询才是真的强大。

    1、find方法查询

      @Test
      public void testQuery() {
          Clazz clazz = entityManager.find(Clazz.class, 5);
          Set<Student> stus = clazz.getStus();
          System.out.println(stus);
      }
    
    Hibernate: select * from t_clazz clazz0_ where clazz0_.id=?
    Hibernate: select * from t_student stus0_ where stus0_.clz_id=?
    

    通过SQL语句可以看出来默认@OneToMany使用懒加载
    2、 JPQL语句查询

      @Test
      public void testJpql() {
          String sql = "select c from Clazz c";
          Query query = entityManager.createQuery(sql);
          List res = query.getResultList();
          System.out.println("条数:" + res.size());
      }
    
    Hibernate: select * from t_clazz
    条数:3
    

    如果JPQL语句中有参数,可以通过query.setParameter(arg0, arg1)方法进行设置,需要注意的是这里的?索引和JDBC相同是从1开始的。

    2、Criteria查询,如果觉得写JPQL语句麻烦,就可以使用这种方式,在SpringData中的动态查询就是使用的这种方式进行构建。其中的注解是我个人理解,我反正就这样记的,如有不正确的地方请指出。

          @Test
      public void testDQuery() {
          // 用来构建查询条件
          CriteriaBuilder cb = entityManager.getCriteriaBuilder();
          // 合成最终的查询语句
          CriteriaQuery<Clazz> query = cb.createQuery(Clazz.class);
          // 通过Root可以获取当前被查询的实体类中的属性,在和CriteriaBuilder创建查询条件
          Root<Clazz> root = query.from(Clazz.class);
          // 通过CriteriaBuilder构建的一个等于的查询条件
          Predicate predicate = cb.equal(root.get("id"), 5);
          // where接收的是一个可变参数,合成所有的查询条件
          query.where(predicate);
          // 传入CriteriaQuery,查询结果
          Clazz clz = entityManager.createQuery(query).getSingleResult();
          
          System.out.println(clz.getName());
      }
    

    CriteriaBuilder中提供了很多的条件,大于小于什么的,基本上可以满足我们实际开发中的要求。

6、总结

  JPA的entityManager和hibernate中的Session是有许多不同的地方的,记得删除就是不一样的,hibernate可以直接通过托管态的对象删除,而JPA是不可以的。JPA作为的是一个标准,Hibernate进行扩展了很多,哇,编不下去了,hibernate学完就没怎么用了,不熟悉就不多说了,这个JPA我会一直跟下去的。
  有时间在整理一份JPA中缓存的使用,还有那个级联处理,各种各样的关系着。然后再着重写一份与SpringData整合的,这个是真的好用,这大概才是JPA真正发挥威力的地方了~~
  其实实在的,我还是比较喜欢ActiveRecord这种模式的ORM框架,python的peewee用着那是真的舒服,奈何java中一直没有找到类似的功能完善,知名度大的(大概是我没认真找吧)。简单看了mybaits plus好像是支持这种模式,有时间要研究研究,不知道大家有没有好的推荐、、、(假装有很多人看我的文章)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容