自從 Java 1.5 開始引入了註解,註解便被廣泛地應用在了各種開源軟件中,使用註解大大地降低了系統中的配置項,讓編程變得更爲優雅。MyBatis 也順應潮流基於註解推出了 MyBatis 的註解版本,避免開發過程中頻繁切換到 XML 或者 Java 代碼中,從而讓開發者使用 MyBatis 會有統一的開發體驗。
因爲最初設計時,MyBatis 是一個 XML 驅動的框架,配置信息是基於 XML 的,而且映射語句也是定義在 XML 中的,而到了 MyBatis 3,就有新選擇了。MyBatis 3 構建在全面且強大的基於 Java 語言的配置 API 之上,這個配置 API 是基於 XML 的 MyBatis 配置的基礎,也是新的基於註解配置的基礎。註解提供了一種簡單的方式來實現簡單映射語句,而不會引入大量的開銷。
註解版
註解版的使用方式和 XML 版本相同,只有在構建 SQL 方面有所區別,所以本課重點介紹兩者之間的差異部分。
相關配置
註解版在 application.properties 只需要指明實體類的包路徑即可,其他保持不變:
mybatis.type-aliases-package=com.neo.model
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
傳參方式
先來介紹一下使用註解版的 MyBatis 如何將參數傳遞到 SQL 中。
直接使用
@Delete("DELETE FROM users WHERE id =#{id}")
void delete(Long id);
在 SQL 中使用 #{id} 來接收同名參數。
使用 @Param
如果你的映射方法的形參有多個,這個註解使用在映射方法的參數上就能爲它們取自定義名字。若不給出自定義名字,多參數則先以 "param" 作前綴,再加上它們的參數位置作爲參數別名。例如,#{param1}、#{param2},這個是默認值。如果註解是 @Param("person"),那麼參數就會被命名爲 #{person}。
@Select("SELECT * FROM users WHERE user_sex = #{user_sex}")
List<User> getListByUserSex(@Param("user_sex") String userSex);
使用 Map
需要傳送多個參數時,可以考慮使用 Map:
@Select("SELECT * FROM users WHERE username=#{username} AND user_sex = #{user_sex}")
List<User> getListByNameAndSex(Map<String, Object> map);
使用時將參數依次加入到 Map 中即可:
Map param= new HashMap();
param.put("username","aa");
param.put("user_sex","MAN");
List<User> users = userMapper.getListByNameAndSex(param);
使用對象
最常用的使用方式是直接使用對象:
@Insert("INSERT INTO users(userName,passWord,user_sex) VALUES(#{userName}, #{passWord}, #{userSex})")
void insert(User user);
在執行時,系統會自動讀取對象的屬性並值賦值到同名的 #{xxx} 中。
註解介紹
註解版最大的特點是具體的 SQL 文件需要寫在 Mapper 類中,取消了 Mapper 的 XML 配置。
上面介紹參數的時候,已經使用了 @Select、@Delete 等標籤,這就是 MyBatis 提供的註解來取代其 XML 文件配置,下面我們一一介紹。
@Select 註解
@Select 主要在查詢的時候使用,查詢類的註解,所有的查詢均使用這個,具體如下:
@Select("SELECT * FROM users WHERE user_sex = #{user_sex}")
List<User> getListByUserSex(@Param("user_sex") String userSex);
@Insert 註解
@Insert,插入數據庫時使用,直接傳入實體類會自動解析屬性到對應的值,示例如下:
@Insert("INSERT INTO users(userName,passWord,user_sex) VALUES(#{userName}, #{passWord}, #{userSex})")
void insert(User user);
@Update 註解
@Update,所有的更新操作 SQL 都可以使用 @Update。
@Update("UPDATE users SET userName=#{userName},nick_name=#{nickName} WHERE id =#{id}")
void update(UserEntity user);
@Delete 註解
@Delete 處理數據刪除。
@Delete("DELETE FROM users WHERE id =#{id}")
void delete(Long id);
以上就是項目中常用的增、刪、改、查,但有時候我們有一些特殊的場景需要處理,比如查詢的對象返回值屬性名和字段名不一致,或者對象的屬性中使用了枚舉。我們期望查詢的返回結果可以將此字段自動轉化爲對應的類型,MyBatis 提供了另外兩個註解來支持:@Results 和 @Result。
@Results 和 @Result 註解
這兩個註解配合來使用,主要作用是將數據庫中查詢到的數值轉化爲具體的字段,修飾返回的結果集,關聯實體類屬性和數據庫字段一一對應,如果實體類屬性和數據庫屬性名保持一致,就不需要這個屬性來修飾。示例如下:
@Select("SELECT * FROM users")
@Results({
@Result(property = "userSex", column = "user_sex", javaType = UserSexEnum.class),
@Result(property = "nickName", column = "nick_name")
})
List<UserEntity> getAll();
爲了更接近實際項目,特地將 user_sex、nick_name 兩個屬性加了下劃線和實體類屬性名不一致,另外 user_sex 使用了枚舉,使用 @Results 和 @Result 即可解決這樣的問題。
注意,使用 # 符號和 $
符號的不同:
// This example creates a prepared statement, something like select * from teacher where name = ?;
@Select("Select * from teacher where name = #{name}")
Teacher selectTeachForGivenName(@Param("name") String name);
// This example creates n inlined statement, something like select * from teacher where name = 'someName';
@Select("Select * from teacher where name = '${name}'")
Teacher selectTeachForGivenName(@Param("name") String name);
同上,上面兩個例子可以發現,使用 # 會對 SQL 進行預處理,使用 $
時拼接 SQL,建議使用 #,使用 $ 有 SQL 注入的可能性。
動態 SQL
MyBatis 最大的特點是可以靈活的支持動態 SQL,在註解版中提供了兩種方式來支持,第一種是使用註解來實現,另一種是提供 SQL 類來支持。
使用註解來實現
用 script 標籤包圍,然後像 XML 語法一樣書寫:
@Update("<script>
update users
<set>
<if test="userName != null">userName=#{userName},</if>
<if test="nickName != null">nick_name=#{nickName},</if>
</set>
where id=#{id}
</script>")
void update(User user);
這種方式就是註解 + XML 的混合使用方式,既有 XML 靈活又有註解的方便,但也有一個缺點需要在 Java 代碼中拼接 XML 語法很不方便,因此 MyBatis 又提供了一種更優雅的使用方式來支持動態構建 SQL。
使用 SQL 構建類來支持
以分頁爲例進行演示,首先定義一個 UserSql 類,提供方法拼接需要執行的 SQL:
public class UserSql {
public String getList(UserParam userParam) {
StringBuffer sql = new StringBuffer("select id, userName, passWord, user_sex as userSex, nick_name as nickName");
sql.append(" from users where 1=1 ");
if (userParam != null) {
if (StringUtils.isNotBlank(userParam.getUserName())) {
sql.append(" and userName = #{userName}");
}
if (StringUtils.isNotBlank(userParam.getUserSex())) {
sql.append(" and user_sex = #{userSex}");
}
}
sql.append(" order by id desc");
sql.append(" limit " + userParam.getBeginLine() + "," + userParam.getPageSize());
log.info("getList sql is :" +sql.toString());
return sql.toString();
}
}
可以看出 UserSql 中有一個方法 getList,使用 StringBuffer 對 SQL 進行拼接,通過 if 判斷來動態構建 SQL,最後方法返回需要執行的 SQL 語句。
接下來只需要在 Mapper 中引入這個類和方法即可。
@SelectProvider(type = UserSql.class, method = "getList")
List<UserEntity> getList(UserParam userParam);
- type:動態生成 SQL 的類
- method:類中具體的方法名
相對於 @SelectProvider 提供查詢 SQL 方法導入,還有 @InsertProvider、@UpdateProvider、@DeleteProvider 提供給插入、更新、刪除的時候使用。
結構化 SQL
可能你會覺得這樣拼接 SQL 很麻煩,SQL 語句太複雜也比較亂,彆着急!MyBatis 給我們提供了一種升級的方案:結構化 SQL。
示例如下:
public String getCount(UserParam userParam) {
String sql= new SQL(){{
SELECT("count(1)");
FROM("users");
if (StringUtils.isNotBlank(userParam.getUserName())) {
WHERE("userName = #{userName}");
}
if (StringUtils.isNotBlank(userParam.getUserSex())) {
WHERE("user_sex = #{userSex}");
}
//從這個 toString 可以看出,其內部使用高效的 StringBuilder 實現 SQL 拼接
}}.toString();
log.info("getCount sql is :" +sql);
return sql;
}
- SELECT 表示要查詢的字段,可以寫多行,多行的 SELECT 會智能地進行合併而不會重複。
- FROM 和 WHERE 跟 SELECT 一樣,可以寫多個參數,也可以在多行重複使用,最終會智能合併而不會報錯。這樣語句適用於寫很長的 SQL,且能夠保證 SQL 結構清楚,便於維護、可讀性高。
更多結構化的 SQL 語法請參考 SQL 語句構建器類。
具體使用和 XML 版本一致,直接注入到使用的類中即可。
多數據源使用
註解版的多數據源使用和 XML 版本的多數據源基本一致。
首先配置多數據源:
mybatis.type-aliases-package=com.neo.model
spring.datasource.test1.jdbc-url=jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.test1.username=root
spring.datasource.test1.password=root
spring.datasource.test1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.test2.jdbc-url=jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.test2.username=root
spring.datasource.test2.password=root
spring.datasource.test2.driver-class-name=com.mysql.cj.jdbc.Driver
分別構建兩個不同的數據源。
DataSource1 配置:
@Configuration
@MapperScan(basePackages = "com.neo.mapper.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSource1Config {
@Bean(name = "test1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.test1")
@Primary
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "test1SqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Bean(name = "test1TransactionManager")
@Primary
public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test1SqlSessionTemplate")
@Primary
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
DataSource2 配置和 DataSource1 配置基本相同,只是去掉了 @Primary。
將以前的 Userapper 分別複製到 test1 和 test2 目錄下,分別作爲兩個不同數據源的 Mapper 來使用。
測試
分別注入兩個不同的 Mapper,想操作哪個數據源就使用哪個數據源的 Mapper 進行操作處理。
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
@Autowired
private User1Mapper user1Mapper;
@Autowired
private User2Mapper user2Mapper;
@Test
public void testInsert() throws Exception {
user1Mapper.insert(new User("aa111", "a123456", UserSexEnum.MAN));
user1Mapper.insert(new User("bb111", "b123456", UserSexEnum.WOMAN));
user2Mapper.insert(new User("cc222", "b123456", UserSexEnum.MAN));
}
}
執行測試用例完成後,檢查 test1 庫中的用戶表有兩條數據,test2 庫中的用戶表有 1 條數據證明測試成功。
如何選擇
兩種模式各有特點,註解版適合簡單快速的模式,在微服務架構中,一般微服務都有自己對應的數據庫,多表連接查詢的需求會大大的降低,會越來越適合註解版。XML 模式比適合大型項目,可以靈活地動態生成 SQL,方便調整 SQL,也有痛痛快快、洋洋灑灑地寫 SQL 的感覺。在具體開發過程中,根據公司業務和團隊技術基礎進行選型即可。