# [Spring Data JPA] 資料庫與物件 [TOC] ## Annotation | 名稱 | 說明 | |-|-| | @Entity | 告訴Spring這是資料model的宣告 | | @Table | 映射到的資料表 | | @Column | 映射到的資料表的欄位| | @Transient | 忽略該屬性映射至資料表[^transient] | | @Id | 該資料表的主鍵(Primary Key) | | @IdClass | 標註該資料表含複合主鍵 | | @Embeddable、@Embedded、@MapsId | 複合主鍵 | | @CreatedDate | 資料建立或修改時會自動賦值(日期) | | @GeneratedValue、@SequenceGenerator|ID生成器| | @OneToOne、@OneToMany、@ManyToOne、@ManyToMany | 聲明資料表(Entity)間的關係,外鍵(Foreign Key)設定 | | @JoinColumn | 關聯資料表(Entity)要對應到的欄位 | | @SecondaryTable、@SecondaryTables | 加入輔助表 | | @Inheritance | 用於指定類之間的繼承關係 | | @NamedEntityGraph | | | @ModelAttribute | 實例化物件 | [^transient]:JPA預設會將Entity類別的所有屬性與資料表的欄位做映射,相關錯誤: [Spring Data JPA @Transient 沒有作用](https://matthung0807.blogspot.com/2021/04/spring-data-jpa-transient-annotation-not-work.html) 資料參考: [(18) 使用 Liquibase 建立所需的 Table 與 ChangeSet 的介紹](https://medium.com/learning-from-jhipster/18-%E9%80%8F%E9%81%8E-liquibase-%E6%93%8D%E4%BD%9C%E8%B3%87%E6%96%99%E5%BA%AB%E4%BB%A5%E5%8F%8A%E5%BB%BA%E7%AB%8B%E6%89%80%E9%9C%80%E7%9A%84-table-changeset%E7%9A%84%E4%BB%8B%E7%B4%B9-fb7120bb2306) ## @ModelAttribute 接收參數並實例化物件 待整理 參考資料: [[SpringMVC]@ModelAttribute 2種用法與解釋( @ModelAttribute — Used on Method and Parameter )](https://b0444135.medium.com/springmvc-modelattribute-2%E7%A8%AE%E7%94%A8%E6%B3%95%E8%88%87%E8%A7%A3%E9%87%8B-modelattribute-used-on-method-and-parameter-db70284c3344) ## ID 數值產生 以下為自訂生成範例 ```=Java! @Entity @Table(name = "User") public class User { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_generator") @SequenceGenerator(name = "sequence_generator", allocationSize = 1, initialValue = 1) private Long id; @Column(name = "name") private String name; } ``` ### @GeneratedValue[^GenerationType] * `strategy` 生成方式 * `GenerationType.AUTO`[^GenerationType.AUTO] 若資料庫無支援數值生成,則改由TABLE生成 (通常是此表: hibernate_sequence )。 * `GenerationType.IDENTITY` 交由資料庫自增 * `GenerationType.SEQUENCE`[^GenerationType.SEQUENCE] 自訂生成規則(自訂生成器),需搭配 @SequenceGenerator * `GenerationType.TABLE` 指定自行在資料庫建立的表來生成 * `generator` 數值產生器的名稱 [^GenerationType]: [(19) 導入Spring-Data-JPA,將資料庫與物件進行綁定與 Sequence 的設定](https://medium.com/learning-from-jhipster/19-%E5%B0%8E%E5%85%A5spring-data-jpa-%E5%B0%87%E8%B3%87%E6%96%99%E5%BA%AB%E8%88%87%E7%89%A9%E4%BB%B6%E9%80%B2%E8%A1%8C%E7%B6%81%E5%AE%9A%E8%88%87-sequence-%E7%9A%84%E8%A8%AD%E5%AE%9A-d96724c03458) [^GenerationType.AUTO]: [聊聊JPA之GenerationType.AUTO](https://www.796t.com/content/1548845834.html) [^GenerationType.SEQUENCE]: [Spring Data JPA 自訂序號產生器 custom sequence generator for Oracle](https://matthung0807.blogspot.com/2021/06/spring-data-jpa-custom-sequence-generator-oracle.html) ### @SequenceGenerator * `sequenceName` 或 `name` 數值產生器的名稱 * `allocationSize` 每次增加的數值,預設為50 * `initialValue` 初始值,預設為0 ## 複合主鍵 待整理 `@Embedded` 參考資料: [@MapsId](https://docs.oracle.com/javaee/6/api/index.html?javax/persistence/MapsId.html) [@JoinTable @ManyToMany](https://hackmd.io/@OceanChiu/ryM5xipxI#Using-a-Composite-Key-in-JPA-%E8%A4%87%E5%90%88%E9%8D%B5) ## 加入輔助表 在一對一的情況下可以使用 `@SecondaryTable` 或 `@SecondaryTables` 註釋,表示要加入輔助表 ```=java! @Entity @Table(name = "MY_TABLE_NAME") @SecondaryTable(name = "OTHER_TABLE", schema = "OTHER_USER", pkJoinColumns = { @PrimaryKeyJoinColumn(name="table_id") } ) public class TableName { ... @Column(name = "other_data", table = "other_user") private String otherData; ... } ``` ## 共享資料表 `@Inheritance` 用於指定類之間的繼承關係,共有三種策略可使用: ### 1. 單表 (Single Table Inheritance) `InheritanceType.SINGLE_TABLE` ,父類與所有子類共享同一個資料表,在表中會有一個用於區分這些子類不同類型的列(通常稱為 discriminator column)。 ```=java! @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="product_type", discriminatorType = DiscriminatorType.STRING) @DiscriminatorValue("not null") //如果 Product 也有使用的話也要加@DiscriminatorValue public class Product { ... } @Entity @DiscriminatorValue("official") public class OfficialCourse extends Product { ... } @Entity @DiscriminatorValue("Trial") public class TrialCourse extends Product { ... } ``` * `@DiscriminatorColumn` 用來區分子類的欄位名稱 * `name` 欄位名稱 * `discriminatorType` 此欄位的資料型態 * `@DiscriminatorValue` 表示子類對應 `@DiscriminatorColumn` 的值 * 可以使用 `not null`、`null` * `@DiscriminatorFormula` 不指定欄位的話,可以用此註釋下條件 ### 2. 表-表 (Table Per Class Inheritance) `InheritanceType.TABLE_PER_CLASS` ,每個實體類對應一個單獨的資料表,子類表包含父類表中的所有字段,並添加自己額外的字段。 ### 3. 類-表 (Joined Table Inheritance) `InheritanceType.JOINED`,每個實體類對應一個單獨的資料表,子類表包含父類表中的所有字段,而子類獨有的字段放在自己的表中。使用外鍵將子類表與父類表關聯起來。 範例: `Animal` 使用了單表繼承,使繼承它的子類共享同一個資料表。 ```=java! @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public class Animal { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // Other fields and methods } @Entity public class Cat extends Animal { // Cat-specific fields and methods } @Entity public class Dog extends Animal { // Dog-specific fields and methods } ``` ## 資料表關聯操作 - CascadeType 與 FetchType[^資料表關聯操作] ### CascadeType * `CascadeType.ALL` 無論儲存、合併、更新或移除,一併對被參考物件作出對應動作。 * `CascadeType.PERSIST` 在儲存時一併儲存被參考的物件。 * `CascadeType.MERGE` 在合併修改時一併合併修改被參考的物件。 * `CascadeType.REMOVE` 在移除時一併移除被參考的物件。 * `CascadeType.REFRESH` 在更新時一併更新被參考的物件。 ### FetchType * `FetchType.LAZY` 除非真正要使用到該屬性的值,否則不會真正將資料從表格中載入物件,預設為此的關聯 `@Basic`、`@OneToOne`、`@ManyToOne` * `FetchType.EARGE` 立即取得資料,預設為此的關聯 `@OneToMany`、`@ManyToMany` [^資料表關聯操作]: [CascadeType 與 FetchType](https://openhome.cc/Gossip/EJB3Gossip/CascadeTypeFetchType.html) ## 解決 N+1 查詢問題 待整理 參考資料: [利用@NamedEntityGraph解决N+1查询问题](https://www.cnblogs.com/echo1937/p/13093418.html) ## oracle 選擇其他用戶的資料表 >jpa或者hibernate连接oracle数据库时,如果提示 ORA-00942 表或视图不存在,如果在确认表名没问题的情况下。可能是因为实体类注解没有配置schema的原因,参考下面即可实现正常连接。 ```=java! @Entity @Table(name="table_name", schema="user_name") public class Entity { ... } ``` ## 需要進行計算但不映射到資料表的屬性,可使用 Hibernate 的 @formula 查詢時會根據給予的公式計算其他欄位的值,不屬於資料表的任何欄位,是虛擬的欄位。除了直接進行計算,也可以使用子查詢。 :::warning 💡 若使用子查詢僅能下原生SQL,無法使用HQL,但ORM會自行帶入此實體類的id(即o.customer_id = id 中的id)。 ::: 如下: ```=java! @Entity @Data @AllArgsConstructor @NoArgsConstructor public class Product implements Serializable { @Id private Long id; private String name; private double price; @Column(name = "check_state") private Boolean checkState; @Formula("price * 0.15") private double salePrice; // o.customer_id = id 中的id為此實體類的id @Formula("(select min(o.creation_date) from Orders o where o.customer_id = id)") private LocalDate firstOrderDate; @Formula(""" ( CASE WHEN ( COALESCE(photo, picture_url) IS NOT NULL AND check_state = 1 ) THEN 1 ELSE 0 END ) """) private Boolean isActivated; } ``` ## 遇到無法創建 Repository 的錯誤 >Error creating bean with name 'xxxRepository' defined in com.demo.repo.XxxRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not a managed type: class com.demo.model.Xxx ### 解法1: 沒有告知 spring xxxRepository 注入的 Xxx 是實體類 在 Xxx 加上 @Entity 即可 ### 解法2: 可能是實體類沒有對應到資料表的欄位 檢查看看是否有誤 若有參數非資料表欄位需補上 @Transient ,否則映射時會發生錯誤 ## 其他深入討論 ### 1. 如更換資料庫廠商,使用Hibernate@Formula 、 Spring Data的@Query 及原生SQL在映射時會因部分SQL語法不支援而產生問題。 解決方法: 1. 更改 SQL 2. 使用 JPQL 3. 使用 jOOQ 框架 參考作法:[使用jOOQ用JPA的本地查询或@Formula来编写供应商无关的SQL](https://juejin.cn/post/7126364023146643493) ### 2. Hibernate 各種註釋的用法 [Hibernate ORM 5.2.18.Final User Guide](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-column-where)