Spring data JPA 相關

Posted by Adam on August 24, 2022
### [Spring JPA – Multiple Databases](https://www.baeldung.com/spring-data-jpa-multiple-databases) pom.xml ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> ``` ```java package poisondog.app.config; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.PlatformTransactionManager; @Configuration @EnableJpaRepositories( basePackages = "com.baeldung.multipledb.dao.repository", entityManagerFactoryRef = "productEntityManager", transactionManagerRef = "productTransactionManager" ) public class PersistenceProductConfiguration { @Primary @Bean(name = "productDataSource", destroyMethod = "") public DataSource productDataSource() { DataSourceBuilder builder = DataSourceBuilder.create(); builder.driverClassName("org.postgresql.Driver"); builder.url("jdbc:postgresql://localhost:5432/testdb"); builder.username("user"); builder.password("password"); return builder.build(); } @Primary @Bean(name = "productEntityManager") public LocalContainerEntityManagerFactoryBean productEntityManager(EntityManagerFactoryBuilder builder, @Qualifier("productDataSource") DataSource datasource) { return builder.dataSource(datasource).packages("com.baeldung.multipledb.model.entity").persistenceUnit("product").build(); } @Primary @Bean(name = "productTransactionManager") public PlatformTransactionManager productTransactionManager(@Qualifier("productEntityManager") EntityManagerFactory factory) { return new JpaTransactionManager(factory); } @Primary @Bean(name = "productJdbcTemplate") public JdbcTemplate productJdbcTemplate(@Qualifier("productDataSource") DataSource datasource) { return new JdbcTemplate(datasource); } } ``` ### JPA SomeToSome #### OneToOne ```java @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "address_id", referencedColumnName = "id") private Address address; // getters and setters } @Entity public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String street; private String city; private String state; private String zipCode; // getters and setters } ``` 在這個示例中,User 實體類包含一個 @OneToOne 註解的屬性 address,它表示一個用戶只對應一個地址。 @JoinColumn 註解指定了 address_id 列作為關聯列,並與 Address 實體類的主鍵列 id 關聯。 值得注意的是,這裡使用了 cascade = CascadeType.ALL 屬性,表示當保存一個 User 實體對象時,同時也會保存與之關聯的 Address 實體對象。這樣可以確保數據的一致性。 除了 @OneToOne 註解,Spring Data JPA 還提供了其他註解來表示不同的關聯關係,例如 @OneToMany、@ManyToOne 和 @ManyToMany。 #### OneToMany ManyToOne ```java @OneToMany(mappedBy = "parentEntity", cascade = CascadeType.ALL) private Set<ChildEntity> childEntities; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_entity_id") private ParentEntity parentEntity; ``` 在JPA中,FetchType被用於定義如何加載實體之間的關聯。FetchType.LAZY表示延遲加載,只有在實際需要使用關聯實體時才加載,而FetchType.EAGER表示立即加載,加載實體時就會加載關聯實體。 除了這兩種類型之外,還有FetchType.SUBSELECT和FetchType.JOIN,它們可以用於更高級的加載策略。FetchType.SUBSELECT表示使用子查詢來加載關聯實體,而FetchType.JOIN表示使用SQL JOIN語句在一次查詢中加載關聯實體。 #### ManyToMany ```java @Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToMany @OrderBy(value = "name ASC, age DESC") // 可搭配 OrderBy 使用 @JoinTable(name = "student_course", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "course_id")) private List<Course> courses; // getters and setters } @Entity public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToMany(mappedBy = "courses") private List<Student> students; // getters and setters } ``` ### @GeneratedValue by SequenceGenerator ```java @Entity @Table(name = "my_table") public class MyEntity { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_sequence_generator") @SequenceGenerator(name = "my_sequence_generator", sequenceName = "MY_SEQUENCE") @Column(name = "id") private Long id; // 其它欄位和方法 } ``` ```sql CREATE SEQUENCE MY_SEQUENCE START WITH 1 INCREMENT BY 1; ``` ### CascadeType CascadeType 是指當對實體執行特定操作(例如刪除、更新等)時,是否要自動操作其相關的實體。 以下是 CascadeType 的幾種常用選項: ALL:當對實體執行任何操作時,自動操作其相關的所有實體。 PERSIST:當執行 persist() 操作時,自動操作其相關的實體。 MERGE:當執行 merge() 操作時,自動操作其相關的實體。 REMOVE:當執行 remove() 操作時,自動操作其相關的實體。 REFRESH:當執行 refresh() 操作時,自動操作其相關的實體。 DETACH:當執行 detach() 操作時,自動操作其相關的實體。 選擇適當的 CascadeType 取決於您的應用程式的需求,例如您是否需要在操作一個實體時,同時自動操作其相關的實體。若未指定CascadeType,預設情況下沒有任何CascadeType被啟用。此時,若在父實體進行保存操作,對應的子實體並不會被自動保存;若想要保存子實體,需要顯式地調用子實體的保存方法。因此,在設計實體關聯時,建議要明確地指定CascadeType,以免忘記調用子實體的保存方法而導致數據不完整的問題。 ### 複合主鍵 ```java @Embeddable public class VoteOptionPK implements Serializable { @Column(name = "vote_id") private Long voteId; @Column(name = "option_id") private Long optionId; // getters and setters } ``` ```java @Entity @Table(name = "vote_option") public class VoteOption { @EmbeddedId private VoteOptionPK id; // 其它欄位... @ManyToOne @MapsId("voteId") // 將 voteId 對應到嵌入式 ID 的欄位 private Vote vote; @ManyToOne @MapsId("optionId") // 將 optionId 對應到嵌入式 ID 的欄位 private Option option; // getters and setters... } ``` ### Repository ```java @Repository public interface YourRepository extends JpaRepository<YourEntity, Long> { // 取得全部並提供排序 List<YourEntity> findAll(Sort sort); // 當某欄位小於某值(LessThan) List<YourEntity> findByYourFieldLessThan(int value); // 當某欄位大於某值(GreaterThan) List<YourEntity> findByYourFieldGreaterThan(int value); // 當某欄位介於兩值中 List<YourEntity> findByYourFieldBetween(int minValue, int maxValue); // 按照 timestamp 欄位降序排列,並返回前 5 條結果。 List<YourEntity> findTop5OrderByTimestampDesc(); Page<YourEntity> findAll(Pageable pageable); @Query("SELECT e FROM YourEntity e WHERE e.name = :name AND e.age > :age") List<YourEntity> findByAgeGreaterThan(@Param("name") String name, @Param("age") int age); } ``` ```java import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { // 刪除指定 ID 的使用者 void deleteById(Long id); // 根據實體名稱刪除使用者 void deleteByEntityName(String entityName); // 根據條件刪除使用者 void deleteByCondition(String condition); } ``` ```java import org.springframework.data.domain.Sort; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; // 創建升序排序的 Sort 對象 Sort sortByYourColumnAsc = Sort.by(Sort.Direction.ASC, "yourColumn"); Sort sortByYourColumnAsc = Sort.by("yourColumn").ascending(); // 創建降序排序的 Sort 對象 Sort sortByYourColumnDesc = Sort.by(Sort.Direction.DESC, "yourColumn"); // pageNumber 是要返回的頁數(從 0 開始),pageSize 是每頁的大小,Sort.by("fieldName").ascending() 用於指定按照哪個字段進行排序,這裡是升序排列。 Pageable pageable = PageRequest.of(pageNumber, pageSize, Sort.by("fieldName").ascending()); Page<YourEntity> page = yourRepository.findAll(pageable); List<YourEntity> content = page.getContent(); // 獲取查詢結果列表 long totalElements = page.getTotalElements(); // 總數 int totalPages = page.getTotalPages(); // 總頁數 ``` ### 自動解析 True or False 假設有一個名為`User`的類別,其中有一個`active`屬性需要使用`@Type(type="yes_no")`來映射到資料庫的`CHAR(1)`欄位,以下是一個`User`類別的範例程式碼: ```java import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import org.hibernate.annotations.Type; @Entity public class User { @Id private Long id; private String username; @Column(columnDefinition = "CHAR(1)") @Type(type = "yes_no") private boolean active; public User() {} public User(Long id, String username, boolean active) { this.id = id; this.username = username; this.active = active; } // Getter and Setter methods public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", active=" + active + '}'; } } ``` 在這個範例中,我們使用`@Type(type="yes_no")`來將`boolean`型別的`active`屬性映射成`CHAR(1)`型別的資料庫欄位。當`active`為`true`時,資料庫欄位的值會是`'Y'`;當`active`為`false`時,資料庫欄位的值會是`'N'`。