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好像是支持这种模式,有时间要研究研究,不知道大家有没有好的推荐、、、(假装有很多人看我的文章)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容