MyBatis 初步 目前我對 MyBatis 的瞭解不是很深,停留在企業比較常用的"資料庫框架"上,系統性的學習要看官方文檔。 這篇隨筆主要圍繞 SpringBoot 中 gradle 環境的搭建來講,是我從《深入淺出SpringBoot2》中討的一些知識。 可以跟著文章做一個基礎環境的項目。 引 ...
MyBatis 初步
目前我對 MyBatis 的瞭解不是很深,停留在企業比較常用的"資料庫框架"上,系統性的學習要看官方文檔。
這篇隨筆主要圍繞 SpringBoot 中 gradle 環境的搭建來講,是我從《深入淺出SpringBoot2》中討的一些知識。
可以跟著文章做一個基礎環境的項目。
引入插件
倉庫地址:mvnrepository 和 阿裡雲
因為用的 gradle ,在 build.gradle 中的 dependencies 中加入依賴包:
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'mysql:mysql-connector-java:8.0.29'
創建數據表和插入數據
終端進入 MySQL 中 xxx 資料庫,輸入 SQL 語句:
CREATE TABLE t_user(
id INT(12) NOT NULL AUTO_INCREMENT,
user_name VARCHAR(60) NOT NULL,
sex INT(3) NOT NULL DEFAULT 1 CHECK (sex in (1,2)),
note VARCHAR(256) NULL,
PRIMARY KEY(id)
);
INSERT INTO t_user(id,user_name,note) VALUES(1,"user_name_1","zhangsan");
創建實體類並設置別名
因為類的全限定名很長,所以使用 @Alias(value = "xxx") 的方式,一般用來和數據表屬性相對的類上(實體類)。
數據表與之對應的實體類如下(自行加入 getter 和 setter ):
package mybatis.pojo;
import mybatis.enumeration.SexEnum;
import org.apache.ibatis.type.Alias;
/**
* @author enrace
* @Alias MyBatis give the class other name.
*/
@Alias(value = "user")
public class User {
private Long id = null;
private String userName = null;
private String note = null;
/**
* The sex is numeration here need use typeHandler to switch.
*/
private SexEnum sex = null;
public User() {}
/** setter 和 getter 方法自行加入即可 **/
}
創建enum 枚舉類型和 typeHandler
typeHandler 是 MyBatis 的重要配置之一,用於不同類型的數據進行自定義轉換。
我學習了將 Java 中的 enum (枚舉類)的實例和資料庫的 int 進行轉換。
先看看 enum 的使用,enum 的代碼如下(自行加入 getter 和 setter ):
package mybatis.enumeration;
/**
* @author enrace
*/
public enum SexEnum {
MALE(1, "男"),
FEMALE(2,"女");
private int id;
private String name;
SexEnum(int id, String name) {
this.id = id;
this.name = name;
}
public static SexEnum getEnumById(int id) {
for (SexEnum sex : SexEnum.values() ) {
if (sex.getId() == id) {
return sex;
}
}
return null;
}
/** setter 和 getter 方法自行加入即可 **/
}
enum 中有兩個類型 MALE 和 FEMALE 分別對應 MySQL 中 int 的 1 和 2。
其中的 getEnumById() 的方法是通過接收 int 返回 enum 的實例。
接著說說 typeHandler,這是通過繼承的方式自定以了一個 typeHandler:
package mybatis.typehandler;
import mybatis.enumeration.SexEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @MappendJdbcTypes Declare JdbcType to be an integer.
* @author enrace
*/
@MappedJdbcTypes(JdbcType.INTEGER)
@MappedTypes(value = SexEnum.class)
public class SexTypeHandler extends BaseTypeHandler<SexEnum> {
/**
* Read gender by column name.
*/
@Override
public SexEnum getNullableResult(ResultSet rs, String col) throws SQLException {
int sex = rs.getInt(col);
if(sex != 1 && sex != 2) {
return null;
}
return SexEnum.getEnumById(sex);
}
/**
* Read gender by subscript.
* @param rs
* @param idx
* @return SexEnum
* @throws SQLException
*/
@Override
public SexEnum getNullableResult(ResultSet rs, int idx) throws SQLException{
int sex = rs.getInt(idx);
if (sex != 1 && sex != 2) {
return null;
}
return SexEnum.getEnumById(sex);
}
/**
* Read gender from a stored procedure.
* @param cs
* @param idx
* @return SexEnum
* @throws SQLException
*/
@Override
public SexEnum getNullableResult(CallableStatement cs, int idx) throws SQLException {
int sex = cs.getInt(idx);
if (sex != 1 && sex != 2) {
return null;
}
return SexEnum.getEnumById(sex);
}
/**
* Set not null gender parameter.
* @param ps
* @param idx
* @param sex
* @param jdbcType
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int idx, SexEnum sex, JdbcType jdbcType)
throws SQLException {
ps.setInt(idx, sex.getId());
}
}
這個類的幾種方法,主要是通過 enum.getEnumById() 返回一個 enum 類型的結果。
我將 SexTypeHandler 分成簡述和細述:
簡敘:
網上瞭解的是: 一個 setxxx 方法,表示向 PreparedStatement 裡面設置值。三個 getxxx 方法,一個是根據列名獲取值,一個是根據列索引位置獲取值,最後一個是存儲過程。
細述:
ResultSet 類型我去查了一下,表示資料庫結果集的數據表,其中的 getXXX 表示在結果集中檢索 XXX 類型。
ResultSet.getInt(col) 方法通過 SQL 子句中指定的列的標簽獲取 sex 的 int。
ResultSet.getInt(idx) 方法通過第幾列的方式從數據表中獲取 sex 的 int。
CallableStatement 類型可以返回一個對象或多個對象ResultSet。
網上瞭解到 CallableStatement 類型用於從Java程式調用存儲過程,存儲過程是我們在資料庫中為某些任務編譯的一組語句。 當我們處理具有複雜場景的多個表時,存儲過程是有益的,而不是向資料庫發送多個查詢,我們可以將所需的數據發送到存儲過程,併在資料庫伺服器本身中執行邏輯。
自己不是很明白存儲過程,不過 CallableStatement 可以獲取到結果,自然能獲取到我們需要的 int 類型。
cs.getInt(idx) 方法通過傳入的 int 檢索指定JDBC INT類型的值。
ps.setInt(idx, sex.getId()) 設置給定的 java int
指定參數值。驅動將一個 SQL INTEGER
值發送到資料庫。
定義 MyBatis 操作介面
操作介面(Mapper 介面)使用來幫助資料庫和 POJO 映射的。
註意: 僅僅為一個介面,不需要任何實現類。
package mybatis.dao;
import mybatis.pojo.User;
import org.springframework.stereotype.Repository;
/**
* @author enrace
*/
@Repository
public interface MyBatisUserDao {
/**
* Get User.
* @param id
* @return User
*/
public User getUser(Long id);
}
@Repository 用於標註數據訪問組件,即 DAO 組件。
除了 操作介面還需要創建映射文件,映射文件的 namespace 是與操作介面對應的。
創建映射文件(小坑)和添加配置
映射文件讓 POJO (類) 能夠與資料庫的數據對應,是 xml 類型的。
主要內容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.dao.MyBatisUserDao">
<select id="getUser" parameterType="long" resultType="user">
select id, user_name as userName, sex, note from t_user where id = #{id}
</select>
</mapper>
主要的幾個屬性:
namespace 指定一個介面,就是需要方法需要執行 sql 的介面。
<select> 標簽代表一個查詢語句。
id 指代這個 SQL,它與介面是同名的(個人認為是映射)。
parameterType 是說明屬性配置為 Long (個人理解為傳入參數類型)。
resultType 這裡指定返回的類型(記得 @Alias 設置的別名就是 user ,那麼到時會返回一個 User 實例)。
然後再去 application.properties 添加如下信息:
# 資料庫 url
spring.datasource.url = jdbc:mysql://localhost:3306/xxx
# 資料庫用戶名
spring.datasource.username = zhangsan
# 資料庫密碼
spring.datasource.password = passwd123
# 最大等待連接中的數量你,設置 0 沒有限制
spring.datasource.tomcat.max-idle = 10
# 最大連接活動數
spring.datasource.tomcat.max-active = 50
# 最大等待毫秒數,單位 ms ,超過時間會出錯誤信息
spring.datasource.tomcat.max-wait = 10000
# 資料庫連接池初始化連接數
spring.datasource.tomcat.initial-size = 5
# 映射文件配置
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
# 掃描別名包,和註解 @Alias 聯用
mybatis.type-aliases-package=mybatis.pojo
# 配置 typeHandler 的掃描包
mybatis.type-handlers-package=mybatis.typehandler
#logging.level.root = DEBUG
#logging.level.org.springframework = DEBUG
#logging.level.org.org.mybatis = DEBUG
小坑
映射文件有了,我將它放在了項目 mybatis.mapper 包下,但是後面執行報了以下異常:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): mybatis.dao.MyBatisUserDao.getUser
分析過程:
classpath 指的文件目錄是什麼?
classpath 指的是 build/resources (gradle 構建包) 或是 target/classes (maven 構建包)。
結果:
發現沒有 mapper 目錄和我的映射文件,手動添加可正常執行。
解決方法一
在 resources 資源目錄創建 mybatis 目錄和 mapper 子目錄,將映射文件放入其中,原因是 resources 中的文件 gradle build 的時候會保留下來。這種是資源文件分離的方式。
解決方法二
build.gradle 中添加下麵的代碼:
sourceSets {
main {
resources {
srcDirs 'src/main/java'
}
}
}
這是資源路徑設置,添加代碼後 gradle build 的時候不會刪除 java 目錄下的 非 .java 尾碼文件。
使用 MapperFactoryBean 裝配 MyBatis 介面
上面的 MyBatisUserDao 是一個 Mapper 介面,不可以使用 new 為其 生成對象實例。需要用到兩個類,它 們 是 MapperFactoryBean 和 MapperScannerConfigurer 。其中 MapperFactoryBean 針對介面配置,MapperScannerConfigurer 則是掃描裝配。書中提到 @MapperScan 可以後面去使用一下,它更為簡便也是用來將對應介面掃描裝配到 Spring IoC 容器中的。
接下來我們創建一個 Bean 來配置 MyBatisUserDao 介面,在 @SpringBootApplication 註解文件下增加代碼:
@Autowired
SqlSessionFactory sqlSessionFactory = null;
/**
* Define a Mapper interface of MyBatis.
* @return MapperFactoryBean\<MyBatisUserDao\>
*/
@Bean
public MapperFactoryBean<MyBatisUserDao> initMyBatisUserDao() {
MapperFactoryBean<MyBatisUserDao> bean = new MapperFactoryBean<>();
bean.setMapperInterface(MyBatisUserDao.class);
bean.setSqlSessionFactory(sqlSessionFactory);
return bean;
}
SqlSessionFactory 是 Spring Boot 自動為我們生成的。
開發服務層
由於不是很難理解,直接上代碼。
服務介面類代碼,如下:
package mybatis.service;
import mybatis.pojo.User;
/**
* @author enrace
*/
public interface MyBatisUserService {
/**
* Get user object.
* @param id
* @return User
*/
public User getUser(Long id);
}
實現類代碼,如下:
package mybatis.service.impl;
import mybatis.dao.MyBatisUserDao;
import mybatis.pojo.User;
import mybatis.service.MyBatisUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author enrace
*/
@Service
public class MyBatisUserServiceImpl implements MyBatisUserService {
@Autowired
private MyBatisUserDao myBatisUserDao = null;
@Override
public User getUser(Long id) {
return myBatisUserDao.getUser(id);
}
}
實現類中,通過 @Autowired 自動裝配 MyBatisUserDao 的 Bean ,我們就實現了 getUser() 方法,從而可以獲得 User 對象。
創建控制器
有了控制器就可以通過 url 測試結果。
控制器代碼,如下:
package mybatis.controller;
import mybatis.pojo.User;
import mybatis.service.MyBatisUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author enrace
*/
@RestController
public class MyBatisController {
@Autowired
private MyBatisUserService myBatisUserService = null;
@RequestMapping("/getUser")
public User getUser(Long id) {
return myBatisUserService.getUser(id);
}
}
@RequestMapping("/getUser") 設置請求的映射,通過傳入 id 得到用戶( JSON )格式。
完成
啟動 Spring Boot 項目,訪問 localhost:8080/getUser?id = 1
至此,您已經瞭解到了 MyBatis 基本的執行過程,祝:事事無憂,天天無 BUG。