JPA是java中的持久層API,sun公司希望通過jpa整合orm技術(shù),實(shí)現(xiàn)天下歸一。JPA作為orm的規(guī)范,我是很有興趣的把它學(xué)習(xí)了一遍。先說說jpa單獨(dú)的使用,在介紹jpa與springdata的整合。
1、創(chuàng)建java項(xiàng)目
呃、這不就過了吧,也可以用eclipse建立jpa項(xiàng)目,學(xué)習(xí)測試嘛,都一樣。
2、導(dǎo)入jar包
jpa作為接口,其自身是沒有任何實(shí)現(xiàn)的,這里我們使用hibernate作為實(shí)現(xiàn)產(chǎn)品。lib/require
下的所有jar文件。我使用的hibernate
版本是5.2.10
的。
還要導(dǎo)入mysql驅(qū)動,加入build path。如下圖:
3、編寫配置文件
主要設(shè)置jpa實(shí)現(xiàn)產(chǎn)品,繼承javax.persistence.spi.PersistenceProvider
接口的類,實(shí)體類的引用路徑。
jpa的基本參數(shù)(數(shù)據(jù)庫驅(qū)動賬號密碼等),實(shí)現(xiàn)產(chǎn)品的配置參數(shù)(如hibernate中的顯示sql語句)。
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" ......>
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
<!-- jpa的實(shí)現(xiàn)產(chǎn)品 -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- 兩個實(shí)體類 -->
<class>cn.lkangle.entity.Student</class>
<class>cn.lkangle.entity.Clazz</class>
<properties>
<!-- 數(shù)據(jù)庫連接配置 -->
<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、創(chuàng)建學(xué)生和班級實(shí)體類
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;
/**
* 學(xué)生的實(shí)體類
* @author lbxx
* 常用注解解釋:
* @Entity 用來標(biāo)示這個類是實(shí)體類
* @Table 實(shí)體類與數(shù)據(jù)表的映射,通過name確定在表名(默認(rèn)類名為表名)
* @Id 主鍵注解,表示該字段為主鍵
* @GeneratedValue 定義主鍵規(guī)則,默認(rèn)AUTO
* @Column 類屬性與表字段映射注解,其中可以設(shè)置在字段名,長度等信息
* @ManyToOne 多對一,可以設(shè)置數(shù)據(jù)加載方式等 默認(rèn)加載方式是EAGER 就是使用left join
* @OneToMany 一對多 默認(rèn)加載方式是 LAZY 懶加載
* @JoinColumn 與*對*配合使用,用來設(shè)置外鍵名等信息
* @Basic 實(shí)體類中會默認(rèn)為每一個屬性加上這個注解,表示與數(shù)據(jù)表存在關(guān)聯(lián),
* 沒有使用Column注解的類屬性會以屬性名作為字段名,駝峰命名需要轉(zhuǎn)為_
* @Temporal 對于Date屬性的格式化注解,有 TIME,DATE,TIMESTAMP 幾個選擇
* @Transient 若存在不想與數(shù)據(jù)表映射的屬性,則需要加上該注解
*/
@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
因?yàn)榫褪菍W(xué)習(xí)使用,這里就使用junit
進(jìn)行測試。
使用jpa首先要通過
Persistence
創(chuàng)建一個EntityManagerFactory
實(shí)例,,然后利用它創(chuàng)建EntityManage
實(shí)例,在通過EntityManage
獲取事務(wù),開始事務(wù)進(jìn)行crud操作,提交事務(wù)、、、一個基本流程就這樣子。
- 建立基本的測試流程
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,
* 傳入?yún)?shù)對應(yīng)配置文件中持久化單元persistence-unit的name
* 通過EntityManagerFactory創(chuàng)建EntityManager
* 獲取EntityTransaction
* 開啟事務(wù)
*/
entityManagerFactory = Persistence.createEntityManagerFactory("jpa");
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
}
@After
public void distory() {
/**
* 提交事務(wù)
* 關(guān)閉entityManager
* 關(guān)閉entityManagerFactory
*/
transaction.commit();
entityManager.close();
entityManagerFactory.close();
}
@Test
public void test() {
}
}
運(yùn)行test可以看見控制臺輸出了hibernate
的鍵表語句,數(shù)據(jù)庫中也創(chuàng)建了數(shù)據(jù)表
- 增加操作
/**
* 添加操作
* 在設(shè)置學(xué)生班級的時候這個班級必須是被jpa管理的持久化對象才能被設(shè)置成功
* 需要先保存班級在保存學(xué)生
*/
@Test
public void testAdd() {
Clazz clz1 = new Clazz();
clz1.setName("計(jì)科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、刪除學(xué)生,直接刪除/** * 被刪除的對象也必須是被jpa管理的持久化對象 */ @Test public void testDeleteStu() { Student stu = entityManager.find(Student.class, 7); entityManager.remove(stu); }
2、刪除班級,因?yàn)橥ㄟ^外鍵建立了關(guān)系,直接刪除會報(bào)錯
報(bào)錯信息: 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`))
解決方法:
- 可以在一方進(jìn)行級聯(lián)設(shè)置
@OneToMany(cascade = {CascadeType.REMOVE}, mappedBy = "clazz") private Set<Student> cls = new HashSet<>();
這樣在刪除班級的時候會連同班級下所有的學(xué)生一起刪除,這是一種很危險(xiǎn)的級聯(lián)方式,不建議使用。
- 通過獲取班級下所有的學(xué)生,先解除關(guān)系在進(jìn)行刪除,不需要設(shè)置級聯(lián)關(guān)系
@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); }
產(chǎn)生的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); }
產(chǎn)生的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、根據(jù)持久化對象性質(zhì),直接修改。修改后jpa會在事務(wù)提交時自動檢查緩存中的內(nèi)容是否和數(shù)據(jù)庫中一致,不一致就會更新。
@Test public void testChange() { Clazz clz = entityManager.find(Clazz.class, 5); Student student = entityManager.find(Student.class, 4); student.setClazz(clz); }
產(chǎn)生的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語句中看的出來,每次更新都會對所有的字段進(jìn)行更新,這樣的話使用第一種方式就要求我們要把對象屬性都進(jìn)行設(shè)置,不然就會出現(xiàn)null值。
-
查詢操作
到了最復(fù)雜的查詢了,其實(shí)這一塊我并不熟悉,因?yàn)閷?shí)際使用的時候都是和springdata集成的,他提供的各種查詢才是真的強(qiáng)大。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語句可以看出來默認(rèn)
@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("條數(shù):" + res.size()); }
Hibernate: select * from t_clazz 條數(shù):3
如果JPQL語句中有參數(shù),可以通過
query.setParameter(arg0, arg1)
方法進(jìn)行設(shè)置,需要注意的是這里的?
索引和JDBC相同是從1
開始的。2、Criteria查詢,如果覺得寫JPQL語句麻煩,就可以使用這種方式,在SpringData中的動態(tài)查詢就是使用的這種方式進(jìn)行構(gòu)建。其中的注解是我個人理解,我反正就這樣記的,如有不正確的地方請指出。
@Test public void testDQuery() { // 用來構(gòu)建查詢條件 CriteriaBuilder cb = entityManager.getCriteriaBuilder(); // 合成最終的查詢語句 CriteriaQuery<Clazz> query = cb.createQuery(Clazz.class); // 通過Root可以獲取當(dāng)前被查詢的實(shí)體類中的屬性,在和CriteriaBuilder創(chuàng)建查詢條件 Root<Clazz> root = query.from(Clazz.class); // 通過CriteriaBuilder構(gòu)建的一個等于的查詢條件 Predicate predicate = cb.equal(root.get("id"), 5); // where接收的是一個可變參數(shù),合成所有的查詢條件 query.where(predicate); // 傳入CriteriaQuery,查詢結(jié)果 Clazz clz = entityManager.createQuery(query).getSingleResult(); System.out.println(clz.getName()); }
CriteriaBuilder
中提供了很多的條件,大于小于什么的,基本上可以滿足我們實(shí)際開發(fā)中的要求。
6、總結(jié)
??JPA的entityManager和hibernate中的Session是有許多不同的地方的,記得刪除就是不一樣的,hibernate可以直接通過托管態(tài)的對象刪除,而JPA是不可以的。JPA作為的是一個標(biāo)準(zhǔn),Hibernate進(jìn)行擴(kuò)展了很多,哇,編不下去了,hibernate學(xué)完就沒怎么用了,不熟悉就不多說了,這個JPA我會一直跟下去的。
??有時間在整理一份JPA中緩存的使用,還有那個級聯(lián)處理,各種各樣的關(guān)系著。然后再著重寫一份與SpringData整合的,這個是真的好用,這大概才是JPA真正發(fā)揮威力的地方了~~
??其實(shí)實(shí)在的,我還是比較喜歡ActiveRecord這種模式的ORM框架,python的peewee用著那是真的舒服,奈何java中一直沒有找到類似的功能完善,知名度大的(大概是我沒認(rèn)真找吧)。簡單看了mybaits plus好像是支持這種模式,有時間要研究研究,不知道大家有沒有好的推薦、、、(假裝有很多人看我的文章)