重学Springboot系列之整合数据库开发框架---中
- java bean的赋值转换
-
- 为什么要做java bean赋值转换
- BeanUtils和Dozer?
- 引入Dozer(6.2.0)
- 自定义类型转换(非对称类型转换)
-
- 映射localDateTime的问题
- 整合MybatisGenerator操作数据
-
- 整合Mybatis
- 安装Mybatis generator插件
- 增删改查实现代码
- 测试一下
- 附录:自动生产代码使用说明
- 整合mybatisPlus操作数据库
-
- SpringBoot集成MybatisPlus
- Mapper继承实现
- MybatisPlus与Mybatis XML
- Mybatis开发最佳实践总结
-
- 面对场景的最佳实践
- 场景一:单表的增删改查
- 场景二: 多查询条件的查询(或多表关联查询)
- 场景三: 除上面两种场景外的其他场景
- 查询结果属性映射的最佳实践
- 使用@MapperScan而不是@mapper
- 使用PageHelper分页插件
- 将XxxxMapper.java文件和XxxxMapper.xml文件放在同一个目录下面
- Spring mybatis的多数据源实现
-
- 修改application.yml为双数据源
- 主数据源配置
- 第二个数据源配置
- 测试用例
- spring boot 2.0 报错:“jdbcUrl is required with driverClassName.” 解决办法!
java bean的赋值转换
为什么要做java bean赋值转换
在实际的开发过程中,由于业务的复杂性,通常并不能做到一个model实体贯穿持久层、服务层、控制层。通常需要进行实体对象java bean的赋值转换。
PO: persistent object 持久对象,对应数据库中的entity。通常在进行数据库数据存取操作时使用。可以简单的认为一个PO对应数据库中一张表中的一个记录。PO对象里面只有基本数据类型和String类型的属性(如:int、String),与数据库字段是一一对应的。
BO: business object 业务对象,业务对象主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。通常一个BO是多个PO的组合体,比如:PO在查询出来之后,需要经过业务处理,处理过程中对象的属性逐渐变复杂,有嵌套的数组,对象数组等等。
VO: view object,主要与web页面的展示结构相对应,所以VO也是前端与后端的数据交换定义。
下图中是一个VO,用于返回给前端web界面,用于渲染的数据内容:
下图是一个PO,用于对数据库表的数据的存取。
大家注意看二者的区别,一个AricleVO不仅包含了Article的数据,还包含了Reader读者的数据。
- 当你需要向数据库里面插入数据的时候,你需要将Article(PO)和Reader(PO)分别作为PO记录插入数据库。
- 当你需要将一篇文章的数据和读者信息返回给页面做渲染的时候,你需要从数据库里面查询Article(PO)和Reader(PO),然后将二者组合映射转换为AricleVO返回给前端。
如果你的业务,可以用一个实体类对象,就可以贯穿持久层到展现层,就没有必要做映射赋值转换,也没有必要去分VO、BO、PO。比如:单表表格数据展现、修改、新增。
BeanUtils和Dozer?
比较常用的java bean赋值转换工具是BeanUtils和Dozer,如果没有BeanUtils和Dozer帮我们进行对象之间的转换赋值,我们会怎么做?
articleVO.setId(article.getId());
articleVO.setAuthor(article.getAuthor());
articleVO.setTitle(article.getTitle());
articleVO.setContent(article.getContent());
articleVO.setCreateTime(article.getCreateTime());
BeanUtils是Spring Boot内自动集成的java bean自动转换工具(apache项目下也有一个BeanUtils,这里专指Spring包下面的BeanUtils),使用非常方便。可以通过下面的方法将article(PO) 转换为articleVO。
ArticleVO articleVO = new ArticleVO();
BeanUtils.copyProperties(article,articleVO);
dozer是一个能把实体和实体之间进行转换的工具.只要建立好映射关系.就像是ORM的数据库和实体映射一样。dozer的功能比BeanUtils功能更强大,但是BeanUtils的性能更好。所以简单的同名同类型属性赋值转换使用BeanUtils,复杂的级联结构的属性赋值转换使用Dozer
- Dozer可以实现Integer、Long等基础类型与String数据类型的属性之间的转换(只要名字相同就可以了,数据类型可以不同),BeanUtils只能做到同数据类型同名的属性之间赋值。
- Dozer可以实现递归级联结构的对象赋值,BeanUtils(Spring包下面的)也可以
- Dozer可以实现复杂的数据转换关系,通过xml配置的方式,BeanUtils做不到
使用方法示例如下:
Mapper mapper = DozerBeanMapperBuilder.buildDefault();
// article(PO) -> articleVO
ArticleVO articleVO = mapper .map(article, ArticleVO.class);
这段示例代码。将从数据库里面查询得到的PO对象article,转换为VO对象articleVO,转换过程将所有同名同类型的数据自动赋值给articleVO的成员变量,当然除了reader(因为PO里面没有reader数组数据)。转换需要写属性之间的映射么?不! 默认是根据属性名称来匹配的.
引入Dozer(6.2.0)
从6.2.0版本开始,dozer官方为我们提供了dozer-spring-boot-starter,这样我们在spring boot里面使用dozer更方便了。
<dependency>
<groupId>com.github.dozermapper</groupId>
<artifactId>dozer-spring-boot-starter</artifactId>
<version>6.2.0</version>
</dependency>
在实际开发中,我们不只需要PO转VO,有时还需要List转List.写一个工具类,实现List转List
public class DozerUtils {
static Mapper mapper = DozerBeanMapperBuilder.buildDefault();
public static <T> List<T> mapList(Collection sourceList, Class<T> destinationClass){
List destinationList = new ArrayList();
for (Iterator i$ = sourceList.iterator(); i$.hasNext();){
Object sourceObject = i$.next();
Object destinationObject = mapper.map(sourceObject, destinationClass);
destinationList.add(destinationObject);
}
return destinationList;
}
}
自定义类型转换(非对称类型转换)
在平时的开发中,我们的VO和PO的同名字段尽量是类型一致的。String属性->String属性,Date属性 -> Date属性,但是也不排除有的朋友由于最开始的设计失误
- 需要String属性 -> Date属性,或者ClassA转ClassB呢?这种我们该如何实现呢?
- 或者需要createDate 转 cDate这种属性名称都不一样的,怎么做。
比如下面的两个测试model,进行属性自动赋值转换映射。
@Data
@AllArgsConstructor
public class TestA{
public String name;
public String createDate; //注意这里名称不一样,类型不一样
}
@Data
@NoArgsConstructor
public class TestB{
public String name;
public Date cDate; //注意这里名称不一样,类型不一样
}
然后,我们需要自己去创建转换对应关系,比如:resources/dozer/dozer-mapping.xml。xml内容看上去复杂,其实核心结构很简单。就是class-a到classb的转换,filed用来定义特殊字段(名称或类型不一致)。configuration可以做全局的配置,date-format对所有的日期字符串转换生效。
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozermapper.github.io/schema/bean-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping
http://dozermapper.github.io/schema/bean-mapping.xsd">
<configuration>
<date-format>yyyy-MM-dd HH:mm:ss</date-format>
</configuration>
<mapping>
<class-a>com.dhy.bootlaunch.dozer.TestA</class-a>
<class-b>com.dhy.bootlaunch.dozer.TestB</class-b>
<field>
<a>createDate</a>
<b>cDate</b>
</field>
</mapping>
</mappings>
然后把dozer转换配置文件通知application.yml,进行加载生效
dozer:
mapping-files: classpath:/dozer/dozer-mapping.xml
这样一个对象里面有String属性到Date属性转换的时候,就会自动应用这个转换规则, 不再报错。
@RunWith(SpringRunner.class)
@SpringBootTest
public class DozerTests {
@Test
public void dozerTests() {
Mapper mapper = DozerBeanMapperBuilder
.create().withMappingFiles("dozer/dozer-mapping.xml")
.build();
TestA testA = new TestA("kobe","2020-03-08 11:25:25");
System.out.println(mapper.map(testA,TestB.class));
}
}
输出:
TestB(name=kobe, cDate=Sun Mar 08 11:25:25 CST 2020)
映射localDateTime的问题
net.sf.dozer这个依赖的dozer转换LocalDateTime会出错,但是用com.github.dozermapper这个dozermapper就不会出问题,杠杆亲测,可以正常映射
整合MybatisGenerator操作数据
为了增强Mybatis的功能性和易用性,有两种比较常用的方案
- Mybatis Genenrator
- Mybatis Plus
我们本小节为大家介绍Mybatis Genenrator 的核心用法,下一节为大家介绍Mybatis Plus。
整合Mybatis
第一步:引入maven依赖包,包括mybatis相关依赖包和mysql驱动包。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
第二步:保证application.yml里面有数据库连接的配置。并配置mybatis的xml文件存放位置,下文配置的xml文件目录位置是resources/generator。
spring:
datasource:
url: jdbc:mysql://192.168.161.3:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: test
password: 4rfv$RFV
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:generator/*.xml
logging:
level:
com.dhy.bootlaunch: debug
- mybatis.mapper-locations表示去哪里扫描xml文件
第三步:配置Mybatis的Mapper类文件的包扫描路径
@SpringBootApplication
@MapperScan(basePackages = {"com.dhy.boot.launch.generator"})
public class BootLaunchApplication {
public static void main(String[] args) {
SpringApplication.run(BootLaunchApplication.class, args);
}
}
安装Mybatis generator插件
Mybatis Generator可以自动的帮助我们根据数据库表结构生成持久层的代码,能很大层度上帮助我们提高开发效率。Mybatis generator的使用方法有很多种,比如:
- XML配置文件实现Mybatis Generator代码生成配置
- 编码实现Mybatis Generator代码生成配置
- 通过IDEA插件实现Mybatis Generator代码生成配置
其中最简单易用的就是Mybatis Generator的IDEA插件来生成代码,直观、简单、易用。其实Mybatis Generator插件有很多,笔者就为大家介绍我最常使用的一个:better-mybatis-generator
(免费开源好用)
这个插件将帮助我们根据数据库表结构生成Mybatis操作接口及实体类定义等内容。能极大的方便我们开发,减少手写代码量。
插件怎么安装、如何使用,请点这里?
mybatis代码生成配置详解:
通过该插件,可以帮助我们自动生产Mybatis持久层代码。代码生成完成之后,我们直接使用就可以了。
增删改查实现代码
Service层接口
public interface ArticleRestService {
void saveArticle(ArticleVO article);
void deleteArticle(Long id);
void updateArticle(ArticleVO article);
ArticleVO getArticle(Long id);
List<ArticleVO> getAll();
}
Service接口实现
@Service
public class ArticleMybatisRestService implements ArticleRestService {
@Resource
protected Mapper dozerMapper;
@Resource
private ArticleMapper articleMapper; //由mybatis generator 帮我们自动生成的代码
//新增
@Override
public void saveArticle(ArticleVO article) {
Article articlePO = dozerMapper.map(article,Article.class);
articleMapper.insert(articlePO); //该方法由自动代码生成提供
}
//删除
@Override
public void deleteArticle(Long id) {
articleMapper.deleteByPrimaryKey(id); //该方法由自动代码生成提供
}
//更新
@Override
public void updateArticle(ArticleVO article) {
Article articlePO = dozerMapper.map(article,Article.class);
articleMapper.updateByPrimaryKeySelective(articlePO); //该方法由自动代码生成提供
}
//查询
@Override
public ArticleVO getArticle(Long id) {
//selectByPrimaryKey方法由自动代码生成提供
return dozerMapper.map(articleMapper.selectByPrimaryKey(id),ArticleVO.class);
}
//查询所有
@Override
public List<ArticleVO> getAll() {
List<Article> articles = articleMapper.selectByExample(null); //该方法由自动代码生成提供
return DozerUtils.mapList(articles,ArticleVO.class);
}
}
测试一下
根据Service层函数参数,修改一下控制层Controller代码,使用postman测试一下接口的可用性。
附录:自动生产代码使用说明
使用代码生成工具之后,可以看到它帮助我们自动生成了四种文件:(Xxxxxx为代指,对应数据库表名。如表名叫message,则Xxxxxx代指Message)
- XxxxxxMapper.java,持久层api操作接口
- XxxxxxMapper.xml ,动态sql配置文件
- Xxxxxx的实体类,POJO,Java bean,与数据库表字段一一对应
- XxxxxxExample,数据库单表操作模板,Example可以理解为“条件”。可以作为"查询条件","更新条件“,”删除条件“!
开发规范:
- 自动生成的代码及所在的文件不允许修改,因为数据库可能变化重新生成,导致修改部分的代码丢失。
- 另外数据库表需要设置主键,mysql通常设置id为主键,自增。否则生成的代码及方法数量会减少。
public interface MessageMapper {
//根据"条件"做count(*)
int countByExample(MessageExample example);
//根据"条件"删除记录
int deleteByExample(MessageExample example);
//根据表主键删除记录
int deleteByPrimaryKey(Long id);
//插入一条完整记录,record对象的所有属性都将插入数据库
int insert(Message record);
//插入一条记录,只插入record对象中不为空的属性。
int insertSelective(Message record);
//查询符合"条件"的对象列表
List<Message> selectByExample(MessageExample example);
//根据主键查询对象
Message selectByPrimaryKey(Long id);
//根据example将record中不为空的属性更新到数据库中
int updateByExampleSelective(@Param("record") Message record, @Param("example") MessageExample example);
//根据example将record中所有属性更新到数据库中(所有值覆盖)
//一旦record属性为空,对应的数据库字段不允许为空,则异常
int updateByExample(@Param("record") Message record, @Param("example") MessageExample example);
//根据主键将record中不为空的属性更新到数据库中
int updateByPrimaryKeySelective(Message record);
//根据主键将record中所有属性更新到数据库中(所有值覆盖)
int updateByPrimaryKey(Message record);
}
如何使用Example做sql操作?,Example是条件查询的意思
自动生成的代码比较适合单表简单的sql操作。
- 不适用于多表关联查询,
- 不建议用于带OR的,带IN的,带Exists关系的sql处理。
增加insert
创建增加的对象,并设置要增加对象的内容
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setUserName(userName);
int count = userMapper.insertSelective(sysUser);
写了上面的代码,就不用我们去写SQL操作数据库了。起同样作用的sql如下:
INSERT INTO sys_user (user_id,user_name)
VALUES (#{userId},#{userName});
注意 xxxxmapper中有两个insert方法,其中insertSelective是选择性插入,即:字段有值插入,空字段不做sql插入处理。sys_user表除了user_id,user_name还有其他字段,但是没有作为insert的字段出现。
删除Delete
创建要删除的模板,并设置删除条件
SysUserExample userExample = new SysUserExample();
userExample.createCriteria().andUserIdEqualTo(userId)
.andUserNameEqualTo(userName);
int count = userMapper.deleteByExample(userExample )
写了上面的代码,就不用我们去写SQL操作数据库了。起同样作用的sql如下:
DELETE FROM sys_user
WHERE user_id = #{userId}
AND user_name = #{userName};
修改update
创建修改的对象,并设置要修改对象的内容
SysUser sysUser = new SysUser();
sysUser.setUserName(userName);
创建要修改条件的模板,并设置修改的条件
SysUserExample userExample = new SysUserExample();
userExample.createCriteria().andUserIdEqualTo(userId);
int count = userMapper.updateByExample(sysUser,userExample);
写了上面的代码,就不用我们去写SQL操作数据库了。起同样作用的sql如下:
update sys_user
set user_name = #{userName}
where user_id = #{userId}
简单查询
创建查询的模板并设置查询的条件
SysUserExample userExample = new SysUserExample();
userExample.createCriteria().andUserIdEqualTo(userId);
//根据查询获取登录人信息
SysUser myself = userMapper.selectByExample(userExample).get(0);
写了上面的代码,就不用我们去写SQL操作数据库了。起同样作用的sql如下:
SELECT id,user_id,user_name,`password`,org_id,role_id, phone,address
FROM sys_user
WHERE user_id = ?;
整合mybatisPlus操作数据库
Mybait-plus官网
SpringBoot集成MybatisPlus
第一步:通过maven坐标将mybatis-plus-boot-starter以及数据库驱动引入到Spring Boot项目里面来。注意:引入mybatis-plus-boot-starter的项目就不需要引入mybatis-spring-boot-starter了
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
第二步:保证application.yml里面有数据库连接的配置。
spring:
datasource:
url: jdbc:mysql://192.168.161.3:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: test
password: 4rfv$RFV
driver-class-name: com.mysql.cj.jdbc.Driver
第三步:配置Mybatis的Mapper类文件的包扫描路径
@SpringBootApplication
@MapperScan(basePackages = {"com.dhy.boot.launch.generator","com.dhy.boot.launch.mapper"})
public class BootLaunchApplication {
public static void main(String[] args) {
SpringApplication.run(BootLaunchApplication.class, args);
}
}
Mapper继承实现
如果我们操作数据库中的article表,我们需要按照article表的结构创建一个实体类。
@Data
public class Article {
private Long id;
private String author;
private String content;
private String title;
private Date createtime;
}
然后写一个接口ArticleMapper ,继承自BaseMapper,泛型是Article实体类。
public interface ArticleMapper extends BaseMapper<Article> {
}
BaseMapper中默认帮我们提供了若干的增删改查基础实现,由于ArticleMapper 继承自BaseMapper,所以ArticleMapper 可以使用这些方法去操作数据库的article表。
public interface BaseMapper<T> extends Mapper<T> {
int insert(T entity);
int deleteById(Serializable id);
int deleteByMap(@Param("cm") Map<String, Object> columnMap);
int delete(@Param("ew") Wrapper<T> wrapper);
int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
int updateById(@Param("et") T entity);
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
T selectById(Serializable id);
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
T selectOne(@Param("ew") Wrapper<T> queryWrapper);
Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);
List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
<E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> queryWrapper);
}
MybatisPlus与Mybatis XML
Mybatis Plus只是对Mybatis的增强,所以在使用mybatis plus的项目里面仍然可以使用mybatis xml的语法来实现ORM SQL映射(特别是动态SQL的书写)。
但是需要将扫描路径配置进行简单的调整,原始的配置是这样的
mybatis:
mapper-locations: classpath:generator/*.xml
调整之后的配置是这样的
mybatis-plus:
mapper-locations: classpath:generator/*.xml
Mybatis开发最佳实践总结
面对场景的最佳实践
mybatis代码实现方式
- 使用mybatis generator做代码自动生成,或者使用MybatisPlus解决方案
- 使用XML方式实现
- 使用注解方式实现
以上三种实现方式,有自己适合的应用场景,三种方法全部可以支持。下面是结合笔者多年的mybatis使用经验,总结出在不同的场景下,使用不同的实现方式
场景一:单表的增删改查
mybatis generator生成的代码,或者MybatisPlus能够完成90%的单表操作
,而且不用自己去书写SQL。使用非常方便!
- 这种用法面对开发人员非常友好,有的人说经常用这个会忘记怎么写SQL。我可以斩钉截铁的回答:不会的。因为你脑袋里面没有SQL,是用不明白mybatis generator生成的代码或者Mybstis plus的。
- 但是这种用法虽然简单易用,也会产生一个问题,就是通常写一个关联查询就可以得到的结果,开发人员会倾向于用多次使用单表查询(因为写起来简单,可以犯懒)。说实话性能倒不会一定下降,但代码会很冗余。项目组如果想避免这种情况发生,要特意强调做好规范。
- Mybatis Generator自动生成的代码 最大程度帮你完成单表操作。涉及到关联查询、继承,Mybatis文件和SQL还是要你自己写,但是不要在生成的代码基础上面改!切记!
- 如果使用自动代码生成感觉不适合自己或自己的项目,使用类似于Mybatis-Plus这种第三方增强库,也是很方便的。
场景二: 多查询条件的查询(或多表关联查询)
在web开发中,有一个典型的应用场景是:一个web table页面有多个查询条件,选择填写不同的查询条件得到不同的查询结果,多个查询条件只填写几个条件进行查询,其他的条件不填写。
面对这种场景,就需要ORM框架对 动态SQL(根据传入参数不同,SQL会发生变化) 有很好的支持,包括书写的方便度等。从这个角度上讲,mybatis的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="com.zimug.StudentMapper" >
<select id="findStudent" resultType="com.zimug.bootlaunch.testdb1.model.Student">
SELECT STUD_ID AS studId,name,email,dob
FROM STUDENT
<trim prefix="WHERE" prefixOverrides="AND|OR" suffixOverrides="AND|OR">
<if test="stuId != null" >
AND STUD_ID = #{stuId}
</if>
<if test="name != null and name != '' " >
AND name = #{name}
</if>
<if test="email != null and email != '' " >
AND email= #{email}
</if>
</trim>
</select>
</mapper>
- 当stuId参数为空的时候,AND STUD_ID = #{stuId}查询条件不存在
- 当name 参数为空的时候,AND name = #{name}查询条件不存在,以此类推
public interface StudentMapper {
List<Student> findStudent(@Param("stuId") Integer stuId,
@Param("name") String name,
@Param("email") String email);
}
另外如果你做一个多表的关联查询,不需要使用动态SQL的情况下,使用XML方式也是一个不错的选择。比起在注解方式里面字符串拼SQL要好得多。
场景三: 除上面两种场景外的其他场景
其实除去上面两种场景,剩下的情况已经不多了,但是还是可以举几个例子:
比如:针对单表只有插入操作,你有不想因此生成一套完整的针对单表的操作代码。
比如:只是临时起意,写一个较为简单的SQL。
public interface AnonStudentMapper {
@Select("SELECT STUD_ID AS studId, NAME, EMAIL, DOB " +
"FROM STUDENT " +
"WHERE STUD_ID=#{studId}")
List<Student> findStudentById(Integer studId);
}
可以看到这种方式,最好是SQL在两三行以内,并且没有嵌套SQL,否则会陷入维护的灾难!
查询结果属性映射的最佳实践
使用驼峰映射结果属性(约定大于配置的最佳实践)
Mybatis给我们提供了一种映射方式,如果属性的命名是遵从驼峰命名法的,数据列名遵从下划线命名。这样就可以一劳永逸,无论以后写多少查询SQL都不需要单独制定映射规则。 那么可以使用这种方式,类似如下:
- 实体类属性userName对应SQL的字段user_name;
- 实体类属性userId对应SQL的字段user_id;
在Spring boot环境下只需要写这样一个配置即可。
mybatis:
configuration:
mapUnderscoreToCamelCase: true
其他的实现方式都很不友好,都需要写一个查询SQL,做一套映射配置。
第一种:xml的属性映射举例resultMap
<mapper namespace="data.UserMapper">
<resultMap type="data.User" id="userResultMap">
<!-- 用id属性来映射主键字段 -->
<id property="id" column="user_id"/>
<!-- 用result属性来映射非主键字段 -->
<result property="userName" column="user_name"/>
</resultMap>
</mapper>
第二种:通过注解 @Results 和 @Result
这两个注解是与XML文件中的标签相对应的:
- @Results对应resultMap
- @Result对应result
这两个注解是应用在方法的级别上的,也就是在mapper方法上,如下:
@Select("select * from t_user where user_name = #{userName}")
@Results(
@Result(property = "userId", column = "user_id"),
@Result(property = "userName", column = "user_name")
)
User getUserByName(@Param("userName") String userName);
第三种:通过SQL字段别名来完成映射
@Select("select user_name as userName, user_id as userId from t_user where user_name = #{userName}")
User getUserByName(@Param("userName") String userName);
使用@MapperScan而不是@mapper
@SpringBootApplication
@MapperScan(basePackages = {"com.dhy.**.mapper"})
public class DemoMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(DemoMybatisApplication.class, args);
}
}
这样就会自动扫描com.dhy.**.mapper目录下面的所有XXXXMapper文件,并且完成自动注入。
不需要在每一个Mapper上面都加@Mapper注解。
如下,不需要这么做:
@Mapper
public interface DemoMapper {
@Insert("insert into Demo(name) values(#{name})")
@Options(keyProperty="id",keyColumn="id",useGeneratedKeys=true)
public void save(Demo demo);
}
使用PageHelper分页插件
如果你使用了MybatisPlus,使用MybatisPlus提供的分页方案就好了,和这个PageHelper一样的简单,使用方法几乎一致!
引入maven依赖包
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.10</version>
</dependency>
测试用例,下面的方法查询第一页的数据,每页查询返回2条数据
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisTest {
@Resource
ArticleMapper articleMapper;
@Test
public void testPageHelper(){
// 只有紧跟在PageHelper.startPage方法后的第一个Mybatis的查询(Select)方法会被分页!!!!
PageHelper.startPage(1, 2);
List<Article> articles = articleMapper.selectByExample(null);
PageInfo<Article> page = PageInfo.of(articles);
System.out.println(page);
}
}
将XxxxMapper.java文件和XxxxMapper.xml文件放在同一个目录下面
我们写代码的时候,通常是XxxxMapper.java文件和XxxxMapper.xml文件一起写的,新建和修改几乎都是一起进行。
但是按照springboot和maven的约定,java文件放在/src/main/java下面,xml文件要放在resources目录下面。
这样我们就要来回的切换目录,找文件,很麻烦。可以通过如下pom.xml配置来解决这个问题。通过如下配置,我们就可以将XML文件和java文件都放在/src/main/java下面子目录,在一起。
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>false</filtering>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
这个方案我已经不用了,虽然这是我个人的一个习惯,但是很多新加入项目组的小朋友思想上会有困扰。还是老老实实的遵循/src/main/java目录下面只存放java文件,这是maven的规范要求。
Spring mybatis的多数据源实现
本节采用的多数据源的实现方式,仍然是分包策略(与之前的JPA实现多数据源的方式是一致的)。即:操作接口分包存放,Spring扫描不同的包,自动注入不同的数据源。这种方式实现简单,也是一种“约定大于配置”思想的典型应用。
需要注意的是:本文介绍的这种多数据源的实现方式,只适用于我们自己手写的Mybatis Mapper(及Mapper.xml) 或者是Mybatis Generator生成的代码,不适用于Mybatis plus Mapper。Mybatis Plus 有自己的多数据源实现方案,MP官方网站已经给出讲解,我这里就不写了。
MyBaits-plus多数据源实现
修改application.yml为双数据源
在application.yml配置双数据源,第一个数据源访问testdb库,第二个数据源访问testdb2库
spring:
datasource:
primary:
jdbc-url: jdbc:mysql://192.168.161.3:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: test
password: 4rfv$RFV
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
jdbc-url: jdbc:mysql://192.168.161.3:3306/testdb2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: test
password: 4rfv$RFV
driver-class-name: com.mysql.cj.jdbc.Driver
主数据源配置
去掉SpringBoot程序主入口上的@MapperScan注解,将注解移到下面的MyBatis专用配置类上方。
DataSource数据源、SqlSessionFactory、TransactionManager事务管理器、SqlSessionTemplate依据不同的数据源分别配置。第一组是primary,第二组是secondary。
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
//数据源primary-testdb库接口存放目录
@MapperScan(basePackages = "com.dhy.boot.launch.generator.testdb",
sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryDataSourceConfig {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary") //数据源primary配置
@Primary
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "primarySqlSessionFactory")
@Primary
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//设置XML文件存放位置
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:generator/testdb/*.xml")); //注意这里testdb目录
return bean.getObject();
}
@Bean(name = "primaryTransactionManager")
@Primary
public DataSourceTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "primarySqlSessionTemplate")
@Primary
public SqlSessionTemplate primarySqlSessionTemplate(
@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory)
throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
第二个数据源配置
@Configuration
@MapperScan(basePackages = "com.zimug.boot.launch.generator.testdb2", //注意这里testdb2目录
sqlSessionTemplateRef = "secondarySqlSessionTemplate")
public class SecondaryDataSourceConfig {
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary") //注意这里secondary配置
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(
@Qualifier("secondaryDataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//设置XML文件存放位置
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:generator/testdb2/*.xml")); //注意这里testdb2目录
return bean.getObject();
}
@Bean(name = "secondaryTransactionManager")
public DataSourceTransactionManager secondaryTransactionManager(
@Qualifier("secondaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "secondarySqlSessionTemplate")
public SqlSessionTemplate secondarySqlSessionTemplate(
@Qualifier("secondarySqlSessionFactory") SqlSessionFactory sqlSessionFactory)
throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
测试用例
将自动生成的代码(自己写Mapper和实体类也可以),分别存放于testdb和testdb2两个文件夹
在Service层测试代码
@Override
@Transactional
public ArticleVO saveArticle(ArticleVO article) {
Article articlePO = dozerMapper.map(article,Article.class);
articleMapper.insert(articlePO);
Message message = new Message();
message.setName("kobe");
message.setContent("退役啦");
messageMapper.insert(message);
return article;
}
将上面的代码,写入service接口,在同一个函数中构造article和message对象,并将对象数据调用Mapper插入数据库中。如果article数据插入testdb库的article表中,message数据插入testdb2库的message表中,就表示我们的多数据源配置正确了
spring boot 2.0 报错:“jdbcUrl is required with driverClassName.” 解决办法!
springboot 升级到2.0之后发现配置多数据源的时候报错:
“jdbcUrl is required with driverClassName.”
或者
Cause: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.] with root cause
主要原因是在1.0 配置数据源的过程中主要是写成:spring.datasource.url 和spring.datasource.driverClassName。
而在2.0升级之后需要变更成:spring.datasource.jdbc-url和spring.datasource.driver-class-name即可解决!