第 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_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_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 的感觉。在具体开发过程中,根据公司业务和团
队技术基础进⾏选型即可。
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值