spring boot 支援資料庫、connection pool、transaction 自動設定。資料庫還支援 H2, HSQL, Derby 嵌入式資料庫。NoSQL 支援 Mongodb, Neo4j, Elasticsearch, Redis, GemFire or Geode, Cassandra, Couchbase, LDAP, InfluxDB。比較常用的是 Redis, MongoDB, Elasticsearch。
嵌入式資料庫
存放在記憶體的資料庫
不需要設定 URL,只要引用 library 即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
資料來源 DataSource
java 是透過 javax.sql.DataSource 連接關聯式資料庫。spring boot 只需要引用 spring-boot-starter-data-jdbc 即可,另外還需要該關聯式資料庫的 JDBC driver,例如 MySQL 要用 mysql-connector
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
spring-boot-starter-data-jdbc 包含預設的 HikariCP connection pool
DataSource 自動設定的類別是 DataSourceAutoConfiguration
自動配置檔案 org.springframework.boot.autoconfigure.AutoConfiguration.imports
因為資料庫自動設定類別使用 @ConditionalOnClass
,如果 classpath 裡面有以下兩個類別,就會自動完成設定
DataSource.class
EmbeddedDatabaseType.class
會先設定外部關聯式資料庫 DataSource,再設定嵌入式資料庫。如果沒有自動設定的類別,就會使用 connection pool 的預設設定值
application.yml 的設定參數為 spring.datasource.*
spring:
application:
name: data1
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
#driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://localhost/testweb
username: root
password: password
也可以使用 MariaDB 的 driver
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
</dependency>
driver-class 也要同時修改
driver-class-name: org.mariadb.jdbc.Driver
因 Dialect 在 MySQL 版本有差異,啟動 application 會出現這樣的錯誤資訊
org.hibernate.dialect.Dialect : HHH000511: The 5.5.68 version for [org.hibernate.dialect.MySQLDialect] is no longer supported, hence certain features may not work properly. The minimum supported version is 8.0.0. Check the community dialects project for available legacy versions.
這時候要調整使用的 Dialect
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-community-dialects</artifactId>
</dependency>
application.yml
spring:
jpa:
properties:
hibernate:
# dialect: org.hibernate.dialect.MySQLDialect
dialect: org.hibernate.community.dialect.MySQLLegacyDialect
啟動時,同時會看到 Hikari connection pool 的 log
HikariPool-1 - Starting...
HikariPool-1 - Added connection org.mariadb.jdbc.Connection@3a175162
HikariPool-1 - Start completed.
自訂 DataSource
直接註冊一個實作 DataSource 的 bean 即可
package com.test.data1;
import org.mariadb.jdbc.MariaDbDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration(proxyBeanMethods = false)
public class DbConfig {
@Bean
@ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource() {
return new MariaDbDataSource();
}
}
也可定義兩個 DataSource,但第一個要設定為 @Primary
package com.test.data1;
import org.mariadb.jdbc.MariaDbDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration(proxyBeanMethods = false)
public class DbConfig2 {
@Primary
@Bean
@ConfigurationProperties(prefix="spring.datasource.xx.one")
public DataSource dataSource1() {
return new MariaDbDataSource();
}
@Bean
@ConfigurationProperties(prefix="spring.datasource.xx.two")
public DataSource dataSource2() {
return new MariaDbDataSource();
}
}
透過 AbstractRoutingDataSource#determineCurrentLookupKey 決定哪一個 data source。
Connection Pool
spring boot 預設使用 HikariCP
自動設定類別是 PooledDataSourceConfiguration
spring 提供以下幾種 connection pool,依照 libary dependency 判斷順序選擇
HikariCP
Tomcat
DBCP2
Oracle UCP
如果都沒有,就直接使用JDBC connection
可設定 spring.datasource.type ,這樣就不會自動選擇 connection pool
spring:
datasource:
type: org.apache.commons.dbcp2.BasicDataSource
資料庫初始化
spring boot 可在啟動時處理 DDL, DML
目前是用 SqlInitializationAutoConfiguration 自動設定
對應參數綁定是 SqlInitializationProperties,可透過 spring.sql.init.*
參數設定
spring:
application:
name: data1
datasource:
#driver-class-name: com.mysql.cj.jdbc.Driver
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://localhost/testweb
username: root
password: password
sql:
init:
mode: ALWAYS
continue-on-error: true
schema-locations: sql/create_user.sql
data-locations:
- classpath:sql/insert_user.sql
mode: 資料庫初始化模式
ALWAYS: 會對外部關聯式資料庫初始化,application 每一次啟動,該 sql 都會被執行一次
EMBEDDED: 只會對 embedded database 自動初始化
NEVER
continue-on-error: true,初始化錯誤,要不要繼續執行。如果 mode 為 ALWAYS,就會忽略重複 insert 有 primary key 的資料的錯誤
schema-locations: DDL script
data-locations: DML script
JdbcTemplate
JdbcTemplate 是 spring 用來簡化 JDBC operation 的 wrapper,只需要引用 spring-boot-starter-data-jdbc 就可以使用
自動設定類別是 JdbcTemplateAutoConfiguration,裡面使用了 JdbcTempateConfiguration,有預設的 JdbcTemplate instance,在 application 中注入即可使用
@Slf4j
@RequiredArgsConstructor
@SpringBootApplication
public class Data1Application {
public final JdbcTemplate jdbcTemplate;
public static void main(String[] args) {
SpringApplication.run(Data1Application.class, args);
}
@Bean
@Transactional
public CommandLineRunner commandLineRunner() {
return testJdbcTemplate();
}
private CommandLineRunner testJdbcTemplate() {
return (args) -> {
log.info("testing JdbcTemplate...");
String username = jdbcTemplate.queryForObject("select username from user where id = 1", String.class);
log.info("id = 1, username = {}", username);
};
}
}
自訂 JdbcTemplate
可以在 spring.jdbc.template.*
修改 jdbcTemplate 的設定
spring:
jdbc:
template:
max-rows: 3
private CommandLineRunner testJdbcTemplate2() {
return (args) -> {
log.info("testing JdbcTemplate2...");
List<Map<String, Object>> result= jdbcTemplate.queryForList("select username from user");
log.info("result list size={}", result.size());
};
}
因為設定的限制,回傳的 list size 最多只會是 3
transaction
spring-boot-starter-data-jdbc 會引用 transaction 相關 library,自動設定類別有
TransactionAutoConfiguration
DataSourceTransactionManagerAutoConfiguration
參數綁定類別是 TransactionProperties
spring.transaction.*
參數設定
新增一個 user DTO 物件
package com.test.data1.entity;
import lombok.Data;
import org.springframework.data.relational.core.mapping.Column;
import java.time.LocalDateTime;
@Data
public class UserDO {
private long id;
private String username;
private String phone;
@Column(value = "create_time")
private LocalDateTime createTime;
private int status;
}
製作 DAO interface UserDao.java
package com.test.data1.dao;
public interface UserDao {
void update();
}
UserDaoImpl.java
package com.test.data1.dao.impl;
import com.test.data1.dao.UserDao;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Service
public class UserDaoImpl implements UserDao {
public final JdbcTemplate jdbcTemplate;
@Transactional
@Override
public void update() {
jdbcTemplate.execute("update user set username = 'Petty' where id = 1");
jdbcTemplate.execute("update user set username = 'Yoga' where id = 2");
throw new RuntimeException("test exception");
}
}
Data1Application.java
@Slf4j
@RequiredArgsConstructor
@SpringBootApplication
public class Data1Application {
public final JdbcTemplate jdbcTemplate;
public final UserDao userDao;
public static void main(String[] args) {
SpringApplication.run(Data1Application.class, args);
}
@Bean
@Transactional
public CommandLineRunner commandLineRunner() {
return useJdbcTemplate();
}
private CommandLineRunner useJdbcTemplate() {
return (args) -> {
log.info("using JdbcTemplate...");
BeanPropertyRowMapper<UserDO> rowMapper = new BeanPropertyRowMapper<>(UserDO.class);
UserDO userDO = jdbcTemplate.queryForObject("select * from user where id = 2", rowMapper);
log.info("user info : {}", userDO);
List<UserDO> userDOList = jdbcTemplate.query("select * from user", rowMapper);
log.info("user list: {}", userDOList);
log.info("userDao.update()...");
userDao.update();
List<UserDO> userDOList2 = jdbcTemplate.query("select * from user", rowMapper);
log.info("user list2: {}", userDOList2);
};
}
}
雖然在 update() 裡面修改了 username,但因爲 @Transactional
的關係,資料 rollback 回原本的狀態,修改的資料會被 rollback。
transaction 失敗的原因
Database 不支援 transaction。MySQL 的 MyISAM 不支援 transaction,在 5.5 以後預設是使用 InnoDB,這個才有支援 transaction
沒有被 spring 管理
UserDaoImpl 裡面的
@Service
如果刪除,就不會受到 spring 管理//@Service public class UserDaoImpl implements UserDao { }
@Transactional
的 method 必須要是 public method如果要用在非 public method,必須開啟 AspectJ framework 的靜態代理模式
呼叫內部 method
business method 一定要經過 spring 才能處理 transaction
以下這兩種狀況,transaction 都不會生效
@Service public class UserDaoImpl implements UserDao { public void update1(User user) { //call update2 update2(user); } @Transactional public void update2(User user) { //update user } }
@Service public class UserDaoImpl implements UserDao { @Transactional public void update1(User user) { //call update2 update2(user); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void update2(User user) { //update user } }
解決方法是在 UserDaoImpl 裡面注入自己,用該物件,呼叫另一個 method
@EnableAspectJAutoProxy(exposeProxy = true)
取得當前的 proxy,並呼叫 method
((UserDaoImpl)AopContext.currentProxy()).update2();
沒有配置 transaction manager
必須要有 PlatformTransactionManager 才能處理 transaction
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionalManager(dataSource); }
spring-boot-starter-data-jdbc 裡面自動設定了一個 DataSourceTransactionalManager,所以可直接使用
設定不支援 transaction
@Service public class UserDaoImpl implements UserDao { @Transactional public void update1(User user) { //call update2 update2(user); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void update2(User user) { //update user } }
沒有 throw exception,就不會 rollback
@Service public class UserDaoImpl implements UserDao { @Transactional public void update1(User user) { try{ } catch(Exception e) { } } }
exception 類別不同
因為 spring 預設針對 RuntimeException 進行 rollback,如果該 method throw 的 exception 類別錯誤,也不會觸發 rollback,除非在
@Transactional
設定 rollbackFor Exception 類別@Service public class UserDaoImpl implements UserDao { @Transactional(rollbackFor = Exception.class) public void update1(User user) { try{ } catch(Exception e) { throw new Exception(e.getMessage()); } } }