重学Springboot系列之整合数据库开发框架---中

x33g5p2x  于2021-11-27 转载在 Spring  
字(23.1k)|赞(0)|评价(0)|浏览(979)

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>

Spring mybatis的多数据源实现

本节采用的多数据源的实现方式,仍然是分包策略(与之前的JPA实现多数据源的方式是一致的)。即:操作接口分包存放,Spring扫描不同的包,自动注入不同的数据源。这种方式实现简单,也是一种“约定大于配置”思想的典型应用。

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表中,就表示我们的多数据源配置正确了

相关文章

微信公众号

最新文章

更多