JPA隨記1

1. EntityManager

EntityManager不是線程安全的,而EntityManagerFactory是線程安全的,如果需要向Spring容器注入EntityManager對象喷兼,可使用@PersistenceContext注解园匹,它保證了每個線程使用的EntityManager是獨立的霉旗。

@PersistenceContext
private EntityManager em;

2. 映射關系注解

2.1 一對一關系

/**
 * 客戶表澄步,每個客戶擁有一個賬戶,為一對一關系
 */
@Entity                            // 作為hibernate 實體類
@Table(name = "tb_customer")       // 映射的表明
@Data
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long custId;    //客戶的主鍵
    @Column(name = "cust_name")
    private String custName;  //客戶名稱
    @Column(name = "cust_address")
    private String custAddress;//客戶地址
    @OneToOne(mappedBy = "customer", cascade = CascadeType.ALL, 
              fetch = FetchType.LAZY, orphanRemoval = true, optional=false)
    @JoinColumn(name = "account_id")   // 設置外鍵的字段名
    private Account account;
}
/**
 * 賬戶表目锭,每個賬戶對應一個客戶评汰,一對一關系
 */
@Entity
@Table(name = "tb_account")
@Data
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;  // 未采用@Column注解指定字段名
    private String password;
    @OneToOne                       // 一對一映射
    @JoinColumn(name = "customer_id") // 外鍵ID
    private Customer customer;
}
  1. @Column注解不是必須的纷捞,若未指定該注解的name值,則采用屬性名作為數(shù)據(jù)庫字段名被去;
  2. @JointColumn用于指定表中外鍵的名稱兰绣,此時功能類似@Column,但注意它修飾屬性類型是另一個Entity類编振;
  3. @OneToOne注解可以僅用于其中一個實體類缀辩,也可以同時應用于兩個實體類(比如希望在查詢一對一的兩個實體之一時,可以加載另一個實體踪央,兩個方向都希望可以如此操作的時候)臀玄,在沒有應用該注解的mappedBy屬性時,Hibernate會在標注了@OneToOne的實體類對應的數(shù)據(jù)庫表創(chuàng)建外鍵約束畅蹂,即若兩個實體類都應用了@OneToOne注解的情況下健无,則兩張表都會創(chuàng)建另一張表的外鍵約束,此時兩張互相存在外鍵約束的表液斜,在刪除數(shù)據(jù)和刪除表的時候累贤,會相對麻煩,可以通過該注解的mappedBy屬性指定另一個實體的外鍵屬性名稱少漆,表示當前實體對應的表不維護外檢約束臼膏,由另一個實體對應的表維護外鍵約束;
    外鍵約束

3.1 @OneToOne注解的cascade屬性表示在操作當前實體的時候示损,是否需要級聯(lián)操作對應的外鍵記錄渗磅,相關的操作屬性值有:ALLPERSIST检访、MERGE始鱼、REMOVEREFRESH脆贵、DETACH医清;(查詢默認就支持級聯(lián)操作,沒有相關屬性值)
3.2 @OneToOne注解的fetch屬性表示是否啟用懶加載卖氨,相關的屬性值有:LAZY会烙、EAGER;
3.3 @OneToOne注解的orphanRemoval屬性值為true的時候,表示當被@OneToOne注解修飾的外鍵實體被當前實體設置為null時双泪,進行數(shù)據(jù)庫操作時持搜,JPA會級聯(lián)刪除對應的外鍵記錄;
3.4 @OneToOne注解的optional屬性值表示當前實體是否能夠?qū)耐怄I實體設置為null與否焙矛。

2.2 一對多關系

場景:一個客戶可以有多條消息

@Entity     // 作為hibernate 實體類
@Table(name = "tb_customer")       // 映射的表明
@Data
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long custId; //客戶的主鍵
    @Column(name = "cust_name")
    private String custName;//客戶名稱
    @Column(name = "cust_address")
    private String custAddress;//客戶地址
    // 一對多
    // fetch 默認是懶加載   懶加載的優(yōu)點( 提高查詢性能)
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private List<Message> messages;
}
@Entity
@Table(name = "tb_message")
@Data
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String info;

    public Message(String info) {
        this.info = info;
    }

    // 一定要有、否則查詢就會有問題
    public Message() {
    }
}
  1. @OneToMany注解表示一對多的外鍵關系残腌,由于是一對多村斟,它應該放置在表示一端的實體類中贫导,修飾的屬性應該是集合類型,比如本例的List<Message>蟆盹,而Hibernate會將對應的外鍵字段創(chuàng)建到多端的實體類對應的數(shù)據(jù)庫表上孩灯,同時也會這張表創(chuàng)建對應的外鍵約束;
  2. @OneToMany注解的fetch屬性默認是LAZY逾滥,即默認懶加載峰档,這是因為一對多如果采用即時加載,在需要關聯(lián)很多外鍵表但這些數(shù)據(jù)在業(yè)務上應用不到的情況下將會浪費很多性能在做外連接操作上寨昙。

2.3 多對一關系

場景:一個客戶可以有多條消息

@Entity     // 作為hibernate 實體類
@Table(name = "tb_customer")       // 映射的表明
@Data
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long custId; //客戶的主鍵
    @Column(name = "cust_name")
    private String custName;//客戶名稱
    @Column(name = "cust_address")
    private String custAddress;//客戶地址
    // 一對多
    // fetch 默認是懶加載   懶加載的優(yōu)點( 提高查詢性能)
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private List<Message> messages;
}
@Entity
@Table(name = "tb_message")
@Data
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String info;

    public Message(String info) {
        this.info = info;
    }

    public Message(String info, Customer customer) {
        this.info = info;
        this.customer = customer;
    }

    // 一定要有讥巡、否則查詢就會有問題
    public Message() {
    }

    // 多對一
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    @JoinColumn(name = "customer_id")
    private Customer customer;
}
  1. 如果需要持久化多端的實體集合,建議應用多端實體的Repository進行持久化操作舔哪,如果使用一端的實體類進行持久化欢顷,Hibernate會先插入一端的實體,再插入多端的實體集合捉蚤,最后再對每一條多端的記錄進行update操作抬驴,維護多端記錄的外鍵;


    一端插入

    多端插入
  2. 根據(jù)查詢條件場景的不同缆巧,可以通過多端或者一端查詢多端的數(shù)據(jù)布持,以本場景為例,如果需要查詢客戶的所有記錄陕悬,那么只需要查詢出該客戶鳖链,由于客戶和消息存在一對多的關系,那么當查詢出客戶記錄墩莫,通過客戶訪問消息的時候芙委,Hibernate就會進一步查詢該客戶的所有消息;
 @Test
  @Transactional(readOnly = true)
   public void testR() {
       // 懶加載過程:
       // 1.findById  只會查詢Customer 和其他關聯(lián)的立即加載
       Optional<Customer> customer = repository.findById(1L);
       System.out.println("=====================");
       // 由于輸出狂秦, 會自動調(diào)用customer.toString()
       System.out.println(customer);
   }

而若要查詢是消息的某個狀態(tài)灌侣,改狀態(tài)不綁定某個客戶,此時則可以通過多端的Message實體進行查詢裂问;

  1. 在多端通過命名接口的方式以外鍵作為查詢條件的時候侧啼,接口的名稱應該使用外鍵對應的實體名稱,并且入?yún)搼猛鈸鞂嶓w類堪簿,但可用的查詢條件僅為主鍵ID痊乾,如:
public interface MessageRepository extends PagingAndSortingRepository<Message, Long> {
   // 根據(jù)客戶id查詢所有信息
   // 通過規(guī)定方法名來實現(xiàn)關聯(lián)查詢: 需要通過關聯(lián)屬性來進行匹配
   // 但是只能通過id來進行匹配
   List<Message> findByCustomer(Customer customer);
}

2.3 多對多關系

場景:一個客戶可以有多個角色,一個角色可以賦給多個客戶

@Entity     // 作為hibernate 實體類
@Table(name = "tb_customer")       // 映射的表明
@Data
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long custId; //客戶的主鍵
    @Column(name = "cust_name")
    private String custName;//客戶名稱
    @Column(name = "cust_address")
    private String custAddress;//客戶地址

    // 單向多對多
    @ManyToMany(cascade = CascadeType.ALL)
    /*中間表需要通過@JoinTable來維護外鍵:(不設置也會自動生成)
     * name 指定中間表的名稱
     * joinColumns 設置本表的外鍵名稱
     * inverseJoinColumns 設置關聯(lián)表的外鍵名稱
     * */
    @JoinTable(
            name = "tb_customer_role",
            joinColumns = {@JoinColumn(name = "c_id")},
            inverseJoinColumns = {@JoinColumn(name = "r_id")}
    )
    private List<Role> roles;
}
@Entity
@Table(name="tb_role")
@Data
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="role_name")
    private String rName;

    public Role(String rName) {
        this.rName = rName;
    }

    public Role(Long id, String rName) {
        this.id = id;
        this.rName = rName;
    }

    public Role() {
    }

    @ManyToMany(cascade = CascadeType.ALL)
    private List<Role> roles;
}
  1. 多對多同樣存在單向多對多椭更,以及雙向多對多的配置哪审;
  2. @JoinTable注解,由于@JoinColumn注解只能用于一對一虑瀑、一對多湿滓、多對一映射關系中指定一個外鍵名稱滴须,而多對多映射關系會產(chǎn)生一張中間表,中間表會存在兩張多對多映射關系表的外鍵約束叽奥,因此衍生出@JoinTable注解扔水,可以指定中間表的名稱以及兩個外鍵的名稱;
  3. 注意多對多不適用于級聯(lián)刪除朝氓,即如果應用@ManyToMany注解的cascade屬性魔市,可能會導致Hibernate拋出ConstraintViolationException,
/ *
  * 注意加上
  * @Transactional
    @Commit
    多對多其實不適合刪除赵哲, 因為經(jīng)常出現(xiàn)數(shù)據(jù)出現(xiàn)可能除了和當前這端關聯(lián)還會關聯(lián)另一端待德,此時刪除就會: ConstraintViolationException。
    * 要刪除誓竿, 要保證沒有額外其他另一端數(shù)據(jù)關聯(lián)
   * */
   @Test
   @Transactional
   @Commit
   public void testD() {
       Optional<Customer> customer = repository.findById(14L);
       repository.delete(customer.get());
   }

如下圖所示磅网,采用級聯(lián)操作,當要刪除c_id為14的值得時候筷屡,要級聯(lián)刪除r_id為9和10的記錄涧偷,但這兩條記錄卻被c_id為9的記錄關聯(lián)著,導致無法正常級聯(lián)刪除:


中間表映射關系

2.4 補充

不要試圖直接在代碼中創(chuàng)建一個ID存在于數(shù)據(jù)庫的對象毙死,然后寄希望于Hibernate會將該對象的修改應用到數(shù)據(jù)庫中燎潮,這樣創(chuàng)建出來的對象是游離態(tài)DETACH的,Hibernate無法對其進行管理扼倘,如果需要令Hibernate托管一個對象确封,需要通過Repository從數(shù)據(jù)庫中查詢出該對象。

/**
 * 錯誤例子
 */
@Test
@Transactional
@Commit
public void testC() {
    List<Role> roles = new ArrayList<>();
    roles.add(new Role(9L, "超級管理員"));
    roles.add(new Role(10L, "商品管理員"));

    Customer customer = new Customer();
    customer.setCustName("諸葛");
    customer.setRoles(roles);
    repository.save(customer);
}
/**
  * 正確例子
  * 1. 如果希望保存已有的關聯(lián)數(shù)據(jù) 再菊,就需要從數(shù)據(jù)庫中查出來(持久狀態(tài))爪喘。否則 提示游離狀態(tài)不能持久化;
  * 2. 如果一個業(yè)務方法有多個持久化操作纠拔, 記得加上@Transactional秉剑,否則不能共用一個session;
  * 3. 在單元測試中用到了@Transactional , 如果有增刪改的操作一定要加@Commit
  * 4. 單元測試會認為你的事務方法@Transactional稠诲,只用于測試侦鹏,不會提交事務,從而需要單獨加上@Commit
  */
@Test
@Transactional
@Commit
public void testC() {
    List<Role> roles = new ArrayList<>();
    roles.add(roleRepository.findById(9L).get());
    roles.add(roleRepository.findById(10L).get());

    Customer customer = new Customer();
    customer.setCustName("諸葛");
    customer.setRoles(roles);
    repository.save(customer);
}

3. 樂觀鎖

可以在實體類的屬性上使用@Version標注臀叙,Hibernate將會對當前實體類對應的表創(chuàng)建一個version字段略水,并應用樂觀鎖機制進行數(shù)據(jù)修改操作。

@Entity     // 作為hibernate 實體類
@Table(name = "tb_customer")       // 映射的表明
@Data
public class Customer {
    @Version
    private Long version;
}

4. 操作人審計

  1. 添加如下依賴
<!--spring-test -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.10</version>
    <scope>test</scope>
</dependency>
  1. 添加配置類劝萤,啟用審計功能@EnableJpaAuditing渊涝,注冊返回當前操作用戶的Bean對象AuditorAware<T>
@Configuration          // 標記當前類為配置類   =xml配文件
// 啟動JPA的Repository掃描,就是XML配置中的<jpa:repositories ...>標簽
@EnableJpaRepositories(basePackages="com.tuling.repositories")  
@EnableTransactionManagement  // 開啟事務
@EnableJpaAuditing            // 開啟JPA審計功能
public class SpringDataJPAConfig {

    /** 
     *  數(shù)據(jù)源
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/springdata_jpa?characterEncoding=UTF-8");
        return  dataSource;
    }

    /** 
     * EntityManagerFactory
     */
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        vendorAdapter.setShowSql(true);
        LocalContainerEntityManagerFactoryBean factory 
                                = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.tuling.pojo");
        factory.setDataSource(dataSource());
        return factory;
    }

    /**
     * 事務管理器
     */
    @Bean
    public PlatformTransactionManager transactionManager(
                                                EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }

    // AuditorAware 返回當前用戶,泛型中的類型驶赏,根據(jù)實體類中的操作人類型決定
    @Bean
    public AuditorAware<String> auditorAware(){
        return new AuditorAware() {
            @Override
            public Optional getCurrentAuditor() {
                // 返回當前用戶炸卑,可以從Session既鞠、redis等存儲介質(zhì)中獲取當前用戶
                return Optional.of("xushu");
            }
        };
    }
}
  1. 在實體類中新增添加人煤傍、添加時間、修改人嘱蛋、修改時間相關屬性和注解蚯姆,并在實體類上增加啟用審計功能的注解@EntityListeners(AuditingEntityListener.class),注意這里創(chuàng)建人和修改人的類型洒敏,需要和上面注冊類中返回的AuditorAware<T>泛型一致龄恋;
@Entity     // 作為hibernate 實體類
@Table(name = "tb_customer")       // 映射的表明
@Data
@EntityListeners(AuditingEntityListener.class)
public class Customer {

   // ...
   
   /**
    * 實體創(chuàng)建人
    */ 
   @CreatedBy
   String createdBy;
   /**
    * 實體創(chuàng)建時間
    */
   @Temporal(TemporalType.TIMESTAMP)
   @CreatedDate
   protected Date dateCreated = new Date();
   /**
    * 實體最后修改人
    */ 
   @LastModifiedBy
   String modifiedBy;
   /**
    * 實體修改時間
    */
   @Temporal(TemporalType.TIMESTAMP)
   @LastModifiedDate
   protected Date dateModified = new Date();
}
  1. 經(jīng)過上述步驟,每次新增凶伙、修改記錄時郭毕,SpringData-JPA將會在數(shù)據(jù)庫表內(nèi)維護如下字段:


    操作人審計數(shù)據(jù)庫結(jié)構(gòu)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市函荣,隨后出現(xiàn)的幾起案子显押,更是在濱河造成了極大的恐慌,老刑警劉巖傻挂,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乘碑,死亡現(xiàn)場離奇詭異,居然都是意外死亡金拒,警方通過查閱死者的電腦和手機兽肤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绪抛,“玉大人资铡,你說我怎么就攤上這事〈甭耄” “怎么了笤休?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蛤育。 經(jīng)常有香客問我宛官,道長,這世上最難降的妖魔是什么瓦糕? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任底洗,我火速辦了婚禮,結(jié)果婚禮上咕娄,老公的妹妹穿的比我還像新娘亥揖。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布费变。 她就那樣靜靜地躺著摧扇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挚歧。 梳的紋絲不亂的頭發(fā)上扛稽,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機與錄音滑负,去河邊找鬼在张。 笑死,一個胖子當著我的面吹牛矮慕,可吹牛的內(nèi)容都是我干的帮匾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼痴鳄,長吁一口氣:“原來是場噩夢啊……” “哼瘟斜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起痪寻,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤螺句,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后槽华,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壹蔓,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年猫态,在試婚紗的時候發(fā)現(xiàn)自己被綠了佣蓉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡亲雪,死狀恐怖勇凭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情义辕,我是刑警寧澤虾标,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站灌砖,受9級特大地震影響璧函,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜基显,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一蘸吓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撩幽,春花似錦库继、人聲如沸箩艺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽艺谆。三九已至,卻和暖如春拜英,著一層夾襖步出監(jiān)牢的瞬間静汤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工聊记, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留撒妈,地道東北人恢暖。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓排监,卻偏偏與公主長得像,于是被迫代替她去往敵國和親杰捂。 傳聞我的和親對象是個殘疾皇子舆床,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

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

  • 本節(jié)重點:1、hibernate映射文件2嫁佳、hibernate核心配置文件3挨队、hibernate核心類 1、hib...
    Vincilovfang閱讀 620評論 0 2
  • JPA 概述 Java Persistence API(Java 持久層 API):用于對象持久化的 API 作用...
    熊少文閱讀 655評論 0 0
  • 2017年8月21日 我原本只想簡單記錄一下springboot中應用Jpa的簡單操作蒿往。不想由于hibernate...
    行者N閱讀 6,490評論 0 23
  • 最近學習hibernate注解形式配置POJO類盛垦,將注解的解析記下來,以備以后使用瓤漏。 例1. Hibernate ...
    光劍書架上的書閱讀 1,268評論 0 7
  • Hibernate 序 創(chuàng)建 hibernate 工程示例: 創(chuàng)建工程腾夯,引入 jar 包 創(chuàng)建配置文件 hiber...
    WJunF閱讀 974評論 0 3