1. 環境說明
數據庫:MySQL
連接用戶名:root
連接密碼:123
庫名:mybatis
導入以下測試數據:
-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`name` varchar(255) DEFAULT NULL COMMENT '姓名',
`position` varchar(255) DEFAULT NULL COMMENT '職位',
`salary` double(10,2) DEFAULT NULL COMMENT '薪資',
`department` varchar(255) DEFAULT NULL COMMENT '部門',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of employee
-- ----------------------------
BEGIN;
INSERT INTO `employee` VALUES (1, '張三', 'web開發工程師', 15000.00, '研發軟件部');
INSERT INTO `employee` VALUES (2, '李四', '嵌入式Linux開發工程師', 14000.00, '研發軟件部');
INSERT INTO `employee` VALUES (3, '王五', 'QT開發工程師', 13000.00, '研發軟件部');
INSERT INTO `employee` VALUES (4, '趙明', '硬件工程師', 13000.00, '研發硬件部');
COMMIT;
Employee類代碼如下
public class Employee {
private Integer id;
private String name;
private String position;
private Double salary;
private String department;
public Employee() {
}
public Employee(String name, String position, Double salary, String department) {
this.name = name;
this.position = position;
this.salary = salary;
this.department = department;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", position='" + position + '\'' +
", salary=" + salary +
", department='" + department + '\'' +
'}';
}
}
2. 整合Mybatis註解版
新建項目時,勾選以下依賴
2.1 配置Mybatis
spring:
datasource:
username: root
password: 123
#mysql8以上的驅動包需要指定以下時區
url: jdbc:mysql://127.0.0.1:23306/mybatis?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
注:
[1] 使用MySQL8以上的驅動包需要使用serverTimezone指定時區,否則會報錯
[2] 指定字符編碼的目的是爲了防止中文亂碼。
[3] 使用MySQL8以上的驅動包時,driver-class-name需要指定爲com.mysql.cj.jdbc.Driver,而不是原來的com.mysql.jdbc.Driver
2.2 編寫接口,實現增刪改查操作
新創建接口文件mapper.EmployeeMapper
@Mapper
public interface EmployeeMapper {
//根據id查找單個
@Select("select * from employee where id = #{id}")
Employee getById(Integer id);
//查找全部
@Select("select * from employee")
List<Employee> getAll();
//添加
//使用自增主鍵
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into employee(name,position,salary,department) values(#{name},#{position},#{salary},#{department})")
int add(Employee employee);
@Update("update employee set name = #{name},position = #{position},salary = #{salary}, department = #{department} where id = #{id}")
int update(Employee employee);
@Delete("delete from employee where id = #{id}")
int delete(Integer id);
}
@Mapper註解:標識這個接口是數據庫映射接口,同時將其注入到SpringIOC容器
@Insert、@Delete、@Update、@Select實現增刪該查操作
@Options指定useGeneratedKeys = true,代表自動生成主鍵;用keyProperty指定主鍵是哪一個
2.3 編寫測試類,測試代碼
@SpringBootTest
class EmployeeMapperTest {
@Autowired
EmployeeMapper employeeMapper;
@Test
void getById() {
Employee employee = employeeMapper.getById(1);
System.out.println(employee);
}
@Test
void getAll() {
List<Employee> employees = employeeMapper.getAll();
System.out.println(employees);
}
@Test
void add() {
Employee employee = new Employee("曉張","射頻工程師",14000.00,"研發硬件部");
employeeMapper.add(employee);
}
@Test
void update() {
Employee employee = new Employee("張三","web開發工程師",15000.00,"服務端開發部");
employee.setId(1);
employeeMapper.update(employee);
}
@Test
void delete() {
employeeMapper.delete(5);
}
}
可以看到全部測試通過
從打印的日誌可以看出,SpringBoot底層採用的是HikariPool連接池。
2.4 編寫service,調用Mapper
接口編寫:
public interface IEmployeeService {
Employee getById(Integer id);
List<Employee> getAll();
int add(Employee employee);
int update(Employee employee);
int delete(Integer id);
}
接口實現:
@Service
public class EmployeeService implements IEmployeeService {
@Autowired
EmployeeMapper employeeMapper;
@Override
public Employee getById(Integer id) {
return employeeMapper.getById(id);
}
@Override
public List<Employee> getAll() {
return employeeMapper.getAll();
}
@Override
public int add(Employee employee) {
return employeeMapper.add(employee);
}
@Override
public int update(Employee employee) {
return employeeMapper.update(employee);
}
@Override
public int delete(Integer id) {
return employeeMapper.delete(id);
}
}
2.5 編寫Controller調用Service
@RestController
public class EmployeeController {
@Autowired
IEmployeeService employeeService;
@GetMapping("/employees")
public List<Employee> getAllEmployee()
{
return employeeService.getAll();
}
}
成功獲取到數據
3. 整合阿里Druid連接池
在開始之前先說一下爲什麼要使用阿里Druid連接池,它有什麼優點?
- 提供強大的監控功能(監控執行的SQL語句以及SQL語句的執行狀態、監控Session、查看連接配置等等)
- 運行穩定,在高併發場景中得到了驗證
3.1 引入Druid依賴
在Maven倉庫中直接以Druid爲關鍵字進行搜索,Druid Spring Boot Starter就是我們要找的依賴
將其添加進項目中
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
至此,成功引入了Druid依賴
3.2 切換連接池爲Druid連接池
直接指定spring.datasource.type即可,將其指定爲DruidDataSource
spring:
datasource:
username: root
password: 123
#mysql8以上的驅動包需要指定以下時區
url: jdbc:mysql://127.0.0.1:23306/mybatis?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
3.3 配置Druid
通過配置spring.datasource.druid屬性配置Druid
druid:
# 初始連接個數
initial-size: 5
# 最小連接池數量
min-idle: 5
# 最大連接池數量
max-active: 20
# 獲取連接時最大等待時間,單位爲毫秒
max-wait: 5000
# 狀態監控
filter:
stat:
# 使能狀態監控
enabled: true
db-type: mysql
log-slow-sql: true
slow-sql-millis: 2000
# 監控過濾器
web-stat-filter:
enabled: true
# 配置過濾器不攔截哪些uri
exclusions:
- "*.js"
- "*.gif"
- "*.jpg"
- "*.png"
- "*.css"
- "*.ico"
- "/druid/*"
# druid 監控頁面
stat-view-servlet:
enabled: true
# 配置監控界面uri
url-pattern: /druid/*
reset-enable: false
# 配置登陸用戶名
login-username: root
# 配置登陸密碼
login-password: root
在上面的配置文件中:
- 配置druid.filter.stat的目的是爲了實現監控統計功能
- 監控界面的uri爲/druid
- 登陸用戶名爲root、登陸密碼爲root
3.4 數據監控
訪問Druid監控頁面
可以看到數據庫配置信息
監控web應用
監控執行的SQL語句
至此,Druid整合完畢。
4. 整合Mybatis配置文件版
Mybatis配置文件版與註解版在整合上的區別:
- 不在Mapper類中寫SQL語句,SQL語句寫在映射文件中
- 指定Mybatis核心配置文件的路徑和映射文件路徑
4.1 改寫一下映射接口
@Mapper
public interface EmployeeMapper {
//根據id查找單個
Employee getById(Integer id);
//查找全部
List<Employee> getAll();
//添加
int add(Employee employee);
int update(Employee employee);
int delete(Integer id);
}
4.2 創建Mybatis核心配置文件
在resources下新建mybatis文件夾,並在mybatis文件夾下新創建mybatis-config.xml文件
在新創建的mybatis-config.xml文件中寫入以下內容
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>
至此,mybatis核心配置文件創建完畢
4.3 創建Mybatis映射接口配置文件
在resources/mybatis目錄下創建mapper文件夾,專門存放映射配置文件,並在mapper目錄下創建employeeMapper.xml文件:
employee文件中填入以下內容:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
這裏的namespace對應接口的全路徑名,這樣Mapper配置文件和Mapper接口會自動綁定
-->
<mapper namespace="com.springboot.mapper.EmployeeMapper">
<!--
數據庫中的字段名和對象的屬性名一樣時,可以省略resultMap
-->
<resultMap type="com.springboot.entity.Employee" id="employeeMap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="position" property="position"/>
<result column="salary" property="salary"/>
<result column="department" property="department"/>
</resultMap>
<!--
id和接口方法名寫成一致,這樣可以自動綁定
-->
<select id="getById" resultMap="employeeMap">
select * from employee where id = #{id}
</select>
<select id="getAll" resultMap="employeeMap">
select * from employee
</select>
<insert id="add">
insert into employee(name,position,salary,department) values(#{name},#{position},#{salary},#{department})
</insert>
<update id="update">
update employee set name = #{name},position = #{position},salary = #{salary}, department = #{department} where id = #{id}
</update>
<delete id="delete">
delete from employee where id = #{id}
</delete>
</mapper>
- mapper配置文件的namespace指定爲映射接口的全路徑名,則兩者將自動綁定
- 數據庫中的字段名和對象的屬性名一樣時,可以省略resultMap,此時需要將resultMap="employeeMap"改爲resultType=“Employee對象的全類名”
- 映射語句的id和接口方法名寫成一致,這樣便可以自動綁定
4.4 指定Mybatis配置文件路徑
#配置Mybatis相關映射文件路徑
mybatis:
#映射配置文件路徑
mapper-locations: classpath:mybatis/mapper/*.xml
#核心配置文件路徑
config-location: classpath:mybatis/mybatis-config.xml
全部測試通過,完成!
5. 事務
5.1 什麼是事務,爲什麼要使用事務
以轉賬爲例,A給B轉賬10元:
第一步:先從數據庫中把A的餘額-10
第二步:再從數據庫中把B的餘額+10
假設,執行第一步成功了,但執行第二步失敗了(或執行第一步和第二步中間的某條語句時失敗了,導致根本執行不到第二步)。那將會造成A的餘額減少了,但B的餘額沒有增加的問題。
因爲中間某條可能發生的錯誤,導致整個業務邏輯沒有正確執行完。之前成功操作的數據並不可靠,需要將其回退。
事務的作用就是爲了保證用戶的每一個操作都是可靠的,事務中的每一步操作都必須成功執行,只要有發生異常就回退到事務開始未進行操作的狀態。
我們在往數據庫中寫數據時,一般都要用到事務管理,Spring對此提供了良好的支持。
5.2 使用事務
使用Spring事務管理只需以下兩個步驟:
- 在Spring啓動類使用@EnableTransactionManagement開啓註解事務管理
- 在 Service層方法上添加 @Transactional 進行事務管理
開啓註解事務管理
@SpringBootApplication
@EnableTransactionManagement
public class SpringMybatis01Application {
public static void main(String[] args) {
SpringApplication.run(SpringMybatis01Application.class, args);
}
}
進行事務管理
@Transactional
public void addMulEmployee()
{
//假設下面這幾個員工必須要一起添加
Employee employee1 = new Employee("name1","position1",10000D,"department1");
Employee employee2 = new Employee("name2","position1",10000D,"department1");
Employee employee3 = new Employee("name3","position1",10000D,"department1");
Employee employee4 = new Employee("name4","position1",10000D,"department1");
Employee employee5 = new Employee("name5","position1",10000D,"department1");
employeeMapper.add(employee1);
employeeMapper.add(employee2);
employeeMapper.add(employee3);
employeeMapper.add(employee4);
employeeMapper.add(employee5);
}
中間發生異常,前面添加的數據將會回滾,employee1、employee2、employee3將不會出現在數據庫中。
employeeMapper.add(employee1);
employeeMapper.add(employee2);
employeeMapper.add(employee3);
//模擬異常情況
int i = 1 / 0;
employeeMapper.add(employee4);
employeeMapper.add(employee5);
注:針對MySQL而言,InnoDB引擎支持事務,MyISAM引擎不支持。
5.3 事務隔離級別
隔離級別是指在發生併發的事務之間的隔離程度,與我們開發時候主要相關的場景包括:髒讀、不可重複讀、幻讀。
- 髒度:事務B讀取了事務A未提交的數據,結果事務A回滾了,這就導致事務B讀到了不正確的數據,這就是髒讀。
- 不可重複讀:事務A讀取了數據,然後執行一些邏輯。在此過程中,事務B修改了事務A剛剛讀取的數據。當事務A再次讀取時,發現數據不匹配了,這就是不可重複讀。
- 幻讀:事務A根據查詢條件讀到了N條數據,事務B插入了M條符合事務A查詢條件的數據,導致事務A再次查詢就有N+M條數據了,這就是幻讀。
接下來看一看Spring爲我們提供的幾種事務隔離級別
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
- DEFAULT:使用數據庫的默認隔離級別,大部分數據庫使用的隔離級別是READ_COMMITTED
- READ_UNCOMMITTED:該級別表示事務A可以讀取事務B修改但未提交的數據,該隔離級別不能防止髒讀和不可重複讀。
- READ_COMMITTED:事務A只能讀取事務B已提交的數據,該隔離級別可以防止髒讀。
- REPEATABLE_READ:該隔離級別表示在一個事務執行期間可以多次執行某個相同的查詢,每次返回的內容都一樣,即使數據有變更,該隔離級別可以防止不可重複讀和幻讀。
- SERIALIZABLE:所有事務依次執行,各個事務之間就不可能存在衝突了,該隔離級別可以防止髒讀、幻讀、不可重複讀,但性能很差,一般不用它。
綜合考慮,絕大多數情況下使用READ_COMMITTED級別就好。設置方式:
@Transactional(isolation = Isolation.READ_COMMITTED)
對於大多數數據庫而言,不用設置就可以,因爲默認就是READ_COMMITTED級別
5.4 事務傳播行爲
概念:在開始一個事務之前,已經存在一個事務。此時可以指定這個要開始的事務的執行行爲。
Spring爲我們提供了以下事務傳播行爲:
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
- REQUIRED:默認,如果當前存在事務,則加入該事務,否則新創建一個事務。
- SUPPORTS:如果當前存在事務,則加入該事務,不存在事務,則以非事務方式運行(一般不用)
- MANDATORY:如果當前存在事務,則加入該事務,如果不存在,則拋出異常。
- REQUIRES_NEW:創建一個新事務,如果當前存在其他事務,將其他事務掛起(一般不用)。
- NOT_SUPPORTED:以非事務方式運行,如果當前存在其他事務,則將其他事務掛起(一般不用)。
- NEVER:以非事務方式運行,如果當前存在事務,則拋出異常(一般不用)。
- NESTED:如果當前存在事務,則創建一個新事務,並和原來存在的事務嵌套運行。如果當前不存在事務,則創建一個新事務運行。
事務傳播行爲採用默認方式最好。如果想要指定,可以通過以下方式指定:
@Transactional(propagation = Propagation.REQUIRED)