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