美文网首页
JPA简单使用

JPA简单使用

作者: 咻咻咻i | 来源:发表于2018-02-25 02:23 被阅读0次

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

    相关文章

      网友评论

          本文标题:JPA简单使用

          本文链接:https://www.haomeiwen.com/subject/khwcxftx.html