第 3-3 課:SpringBoot如何優雅地使⽤ MyBatis 註解版

⾃從 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&useUnico
de=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

 
如果你的映射⽅法的形參有多個,這個註解使⽤在映射⽅法的參數上就能爲它們取⾃定義名字。若不給出⾃GitChat
定義名字,多參數則先以 "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}, #{passW
ord}, #{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}, #{passW
ord}, #{userSex})")
void insert(User user);

@Update 註解

@Update,所有的更新操作 SQL 都可以使⽤ @Update

 

@Update("UPDATE users SET userName=#{userName},nick_name=#{nickName} WHERE id =#{i
d}")
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.cla
ss),
 @Result(property = "nickName", column = "nick_name")
})
List<UserEntity> getAll();

 

爲了更接近實際項⽬,特地將 user_sexnick_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_s
ex 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.getPageS
ize());
 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 = "test1
SqlSessionTemplate")
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") D
ataSource dataSource) throws Exception {
 SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
 bean.setDataSource(dataSource);
 return bean.getObject();
 }
 @Bean(name = "test1TransactionManager")
 @Primary
 public DataSourceTransactionManager testTransactionManager(@Qualifier("test1Da
taSource") DataSource dataSource) {
 return new DataSourceTransactionManager(dataSource);
 }
 @Bean(name = "test1SqlSessionTemplate")
 @Primary
 public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFa
ctory") 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 的感覺。在具體開發過程中,根據公司業務和團
隊技術基礎進⾏選型即可。
 
發佈了89 篇原創文章 · 獲贊 19 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章