### [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'`。