JPA簡單使用

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好像是支持這種模式,有時間要研究研究,不知道大家有沒有好的推薦、、、(假裝有很多人看我的文章)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內(nèi)容