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

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重学Springboot系列之整合数据库开发框架---中相关的知识,希望对你有一定的参考价值。


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重学Springboot系列之整合数据库开发框架---下

重学SpringBoot系列之整合静态资源与模板引擎

重学SpringBoot系列之日志框架与全局日志管理

重学Springboot系列之邮件发送的整合与使用

重学SpringBoot系列之EhCache缓存,缓存问题

重学SpringBoot系列之整合分布式文件系统