在关系型数据库中,随处可见表之间的连接,对级联的表进行增删改查也是程序员必备的基础技能。关于Spring Boot整合Mybatis在之前已经详细写过,不熟悉的可以回顾Spring Boot整合Mybatis并完成CRUD操作,这是本文操作的基础。本文先准备一个测试的数据库,然后使用MyBatis Generator进行部分代码自动生成,再以一个例子来展示稍微高级点的操作:使用Mybatis完成级联一对多的CRUD操作。

数据库准备

数据库用到三张表:user表,role表,user_role表。user表用来存储用户的信息;role表用来存储角色信息;user_role表用来将user和role相关联,存储user和role的映射关系,使得一个用户可以有多个角色,每个角色对应其中的一条记录。

新建user表

CREATE TABLE user(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20),
password VARCHAR(20)
)

新建role表并插入数据

CREATE TABLE role(
id INT PRIMARY KEY AUTO_INCREMENT,
rolename VARCHAR(20)
)
INSERT INTO `role`(`rolename`) VALUES ('后台');
INSERT INTO `role`(`rolename`) VALUES ('前端');
INSERT INTO `role`(`rolename`) VALUES ('客户端');
INSERT INTO `role`(`rolename`) VALUES ('AI');
INSERT INTO `role`(`rolename`) VALUES ('大数据');

结果如图:

新建关联表user_role

CREATE TABLE user_role(
id INT PRIMARY KEY AUTO_INCREMENT,
userid INT,
roleid INT
)

自动生成代码

MyBatis Generator 是MyBatis 官方出品的一款代码生成器,为所有版本的MyBatis以及版本2.2.0之后的iBATIS版本生成代码。我们可以使用它自动生成MyBatis的 mapper、dao、entity ,省去最简单的重复代码编写。更多详细情况可以查看官网。

1、pom.xml添加依赖

 <!-- MyBatis Generator插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>
</plugin>

2、在项目根目录下面添加自动生成代码的配置文件generatorConfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--配置文件信息-->
<properties resource="application.properties"/> <!--defaultModelType="flat" 大数据字段,不分表 -->
<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="autoDelimitKeywords" value="true" />
<property name="beginningDelimiter" value="`" />
<property name="endingDelimiter" value="`" />
<property name="javaFileEncoding" value="utf-8" />
<plugin type="org.mybatis.generator.plugins.SerializablePlugin" /> <plugin type="org.mybatis.generator.plugins.ToStringPlugin" /> <!-- 注释 -->
<commentGenerator >
<property name="suppressAllComments" value="true"/><!-- 是否取消注释 -->
<property name="suppressDate" value="true" /> <!-- 是否生成注释代时间戳-->
</commentGenerator> <!--数据库链接-->
<jdbcConnection driverClass="${spring.datasource.driverClassName}"
connectionURL="${spring.datasource.url}"
userId="${spring.datasource.username}"
password="${spring.datasource.password}">
</jdbcConnection> <!-- 类型转换 -->
<javaTypeResolver>
<!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) -->
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver> <!--生成Model类存放位置-->
<javaModelGenerator targetPackage="com.shangguan.mybatis1.entity" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources" >
<property name="enableSubPackages" value="false" />
</sqlMapGenerator> <javaClientGenerator targetPackage="com.shangguan.mybatis1.dao" targetProject="src/main/java" type="XMLMAPPER" >
<property name="enableSubPackages" value="false" />
</javaClientGenerator> <!-- 数据库表的信息 -->
<table tableName="user" enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="true">
<generatedKey column="id" sqlStatement="Mysql" identity="true" />
</table> <table tableName="role" enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="true">
<generatedKey column="id" sqlStatement="Mysql" identity="true" />
</table> <table tableName="user_role" enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="true">
<generatedKey column="id" sqlStatement="Mysql" identity="true" />
</table> </context>
</generatorConfiguration>

3、使用Maven生成代码:

我使用的是Eclipse,工程目录右键Run as --> Maven build,在Goals中输入mybatis-generator:generate命令,点击Run按钮即可自动生成代码。

自动生成代码时遇到的一些坑

报错信息:

Establishing SSL connection without server's identity verification is not recommended.
According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL
connection must be established by default if explicit option isn't set.
For compliance with existing applications not using
SSL the verifyServerCertificate property is set to 'false'.
You need either to explicitly disable SSL by setting useSSL=false,
or set useSSL=true and provide truststore for server certificate verification.

意思是在说不建议在没有服务器身份验证的情况下建立SSL连接。根据MySQL 5.5.45+、5.6.26+和5.7.6+的要求,如果没有设置显式选项,则默认情况下必须建立SSL连接。您需要通过设置useSSL=false显式禁用SSL,或者设置useSSL=true并为服务器证书验证提供信任存储。

解决方案:在配置文件application.properties中数据库连接后面加上&useSSL=true。

虽然Maven提示“BUILD SUCCESS”,但是仔细看生成的代码和数据库是不匹配的,提示代码被重写,报错[WARNING] Table Configuration user matched more than one table (spring_boot..user,mysql..user,webshop..user,jeece-iyangcong..user)...具体信息如图:

这是因为MySQL 8.0版本驱动将参数nullCatalogMeansCurrent的默认值由true改为了false,在使用MyBatis Generator生成表对应的xml等时会扫描整个服务器里面的全部数据库中的表,而不是扫描对应数据库的表。

解决方案:在配置文件application.properties中数据库连接后面加上&nullCatalogMeansCurrent=true。

如果不出意外的话,将会自动生成3个实体类文件,3个dao层文件,3个mapper.xml。这些代码很长且没有技术含量,在这里就不贴出来的,真有需要可以到文末的GitHub地址去查看。

开始CRUD

接下来需要在Service和ServiceImpl中对dao层进行简单的封装,估计大家都知道该怎么写,在这里也先不贴代码了,详见文末的GitHub地址。

添加用户

添加用户的逻辑是这样的:后台需要分两步处理前端传过来的username,password和roleids。第一步把username和password存入user表;第二步把第一步生成的userid和前端传过来的roleids存入user_role表。这两个操作步骤显然是满足事务的ACID特性的。

Spring 支持编程式事务管理和声明式事务管理两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。本文直接使用@Transactional注解实现事务,因为这种方式操作简洁,代码可读性很高。

UserController中增加用户的代码如下:

 @RequestMapping("/addUser")
@Transactional(rollbackFor={RuntimeException.class, Exception.class})
public Result saveUser(@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password,
@RequestParam(value = "roleids") List<Integer> roleids) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
userService.addUser(user);
for(int i=0;i<roleids.size();i++) {
UserRole userRole = new UserRole();
userRole.setUserid(user.getId());
userRole.setRoleid(roleids.get(i));
userRoleService.addUserRole(userRole);
}
return ResultUtils.result(States.errorCode.SUCCESS, "添加成功", null);
}

使用PostMan测试添加用户:

接口返回添加“添加成功”字样,然后去数据库中查看,user表中多了一条数据,user_role表中也插入了三条数据。显然,这正是需要的结果,这是一个正确的操作。

删除用户

删除用户的逻辑和添加用户的逻辑很相似:第一步根据前端传过来的id删除user表中的记录;第二步userid删除user_role表中的记录;这两个步骤也是满足事务特性,也是使用@Transactional注解来实现。

代码如下:

 @RequestMapping("/deleteUserById")
@Transactional(rollbackFor={RuntimeException.class, Exception.class})
public Result deleteUserById(Integer id) {
userService.deleteUserById(id);
List<UserRole> list = userRoleService.selectByUserId(id);
for(int i=0;i<list.size();i++) {
userRoleService.deleteByPrimaryKey(list.get(i).getId());
}
return ResultUtils.result(States.errorCode.SUCCESS, "删除成功", null);
}

使用PostMan测试删除用户:

接口返回添加“删除成功”字样,然后去数据库中查看,user表中id为1的记录被删除了,user_role表中userid为1的三条记录也都删除了。显然,这正是需要的结果。

修改用户

修改用户逻辑:在user表中修改username和password,同时在user_role表中修改用户和角色的映射记录。修改用户和角色映射记录也就是先按照userid进行删除记录,然后再插入新的映射信息。在这里同样使用@Transactional注解来实现事务。

代码如下:

 @RequestMapping("/updateUser")
@Transactional(rollbackFor={RuntimeException.class, Exception.class})
public Result updateUser(@RequestParam(value = "id")Integer id,
@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password,
@RequestParam(value = "roleids") List<Integer> roleids) {
userService.updateUser(id, username, password);
//查找user_role然后按照id进行删除
List<UserRole> list = userRoleService.selectByUserId(id);
for(int i=0;i<list.size();i++) {
userRoleService.deleteByPrimaryKey(list.get(i).getId());
}
//插入新的roleids
for(int i=0;i<roleids.size();i++) {
UserRole record = new UserRole();
record.setUserid(id);
record.setRoleid(roleids.get(i));
userRoleService.addUserRole(record);
}
return ResultUtils.result(States.errorCode.SUCCESS, "更新成功", null);
}

注意:当使用PostMan进行测试的时候,发现报错:org.apache.ibatis.binding.BindingException: Parameter 'username' not found.

Available parameters are [0, 1, 2, param3, param1, param2]。

解决办法:在dao层的Mapper.java代码的参数加上@param注解 。例如:

void updateByPrimaryKey(@Param("id")Integer id,
@Param("username")String username, @Param("password")String
password);

使用PostMan进行测试修改用户:

返回结果没有问题,再去数据库查看,数据库也没有问题,更新操作完成。

查询用户信息

查询用户的信息不仅需要user表中的用户信息,还需要user_role表中的用户角色映射关系,还需要role表的角色信息。这也是需要表之间联合的操作。

本文采用的方法是新建一个AccountDetail类来整合信息。

public class UserDetail {
private Integer userid;
private String username;
private String password;
private List<Integer> roleids;
private List<String> rolenames;
//省略getter和setter
}

这是整合信息的关键代码:

public List<UserDetail> selectAll(){
List<User> userList = new ArrayList<>();
List<UserDetail> details = new ArrayList<>(); try{
userList = userMapper.selectAll();
details = getUserDetails(userList);
}catch(Exception e){
e.printStackTrace();
return details;
}
return details;
} public List<UserDetail> getUserDetails(List<User> lists){
List<UserDetail> details = new ArrayList<>();
if(lists == null || lists.size() < 1){
return details;
}
Map<Integer, String> nameMap = roleService.getNameMap();
for(int i=0; i< lists.size();i++){
User user = lists.get(i);
UserDetail detail = new UserDetail(); detail.setUserid(user.getId());
detail.setUsername(user.getUsername());
detail.setPassword(user.getPassword());
List<Integer> roleids = new ArrayList<>();
List<String> rolenames = new ArrayList<>();
List<UserRole> userroles = userRoleMapper.selectByUserId(user.getId());
for(int j=0;j<userroles.size();j++) {
roleids.add(userroles.get(j).getRoleid());
rolenames.add(nameMap.get(userroles.get(j).getRoleid()));
}
detail.setRoleids(roleids);
detail.setRolenames(rolenames);
details.add(detail);
}
return details;
}

这是封装的接口:

@RequestMapping("/getAllUser")
public Result getAllUser() {
List<UserDetail> list = userService.selectAll();
return ResultUtils.result(States.errorCode.SUCCESS, "查询成功", list);
}

使用PostMan进行测试查询用户信息:

可以看到这个接口返回了所有的用户信息,包括用户的基本信息和角色信息,准确无误。

总结

Spring和MyBatis实现一对多关联的增删改查也有多种方式:可以使用MyBatis来自定义SQL语句来实现;也可以使用Spring的注解结合MyBatis自动生成的代码来实现。我更喜欢后者,因为通过Mybatis Generator自动生成代码以后,这些代码就不需要再修改了,可以直接封装service和controller。希望本文对大家有用。

Springboot整合Mybatis实现级联一对多CRUD操作的更多相关文章

  1. Spring Boot整合Mybatis完成级联一对多CRUD操作

    在关系型数据库中,随处可见表之间的连接,对级联的表进行增删改查也是程序员必备的基础技能.关于Spring Boot整合Mybatis在之前已经详细写过,不熟悉的可以回顾Spring Boot整合Myb ...

  2. SpringBoot整合mybatis使用pageHelper插件进行分页操作

    SpringBoot整合mybatis分页操作 SpringBoot整合Mybatis进行分页操作,这里需要使用Mybatis的分页插件:pageHelper, 关于pageHelper的介绍,请查看 ...

  3. 【java框架】SpringBoot(7) -- SpringBoot整合MyBatis

    1.整合MyBatis操作 前面一篇提到了SpringBoot整合基础的数据源JDBC.Druid操作,实际项目中更常用的还是MyBatis框架,而SpringBoot整合MyBatis进行CRUD也 ...

  4. SpringBoot整合Mybatis之项目结构、数据源

    已经有好些日子没有总结了,不是变懒了,而是我一直在奋力学习springboot的路上,现在也算是完成了第一阶段的学习,今天给各位总结总结. 之前在网上找过不少关于springboot的教程,都是一些比 ...

  5. SpringBoot整合Mybatis【非注解版】

    接上文:SpringBoot整合Mybatis[注解版] 一.项目创建 新建一个工程 ​ 选择Spring Initializr,配置JDK版本 ​ 输入项目名 ​ 选择构建web项目所需的state ...

  6. SpringBoot整合Mybatis注解版---update出现org.apache.ibatis.binding.BindingException: Parameter 'XXX' not found. Available parameters are [arg1, arg0, param1, param2]

    SpringBoot整合Mybatis注解版---update时出现的问题 问题描述: 1.sql建表语句 DROP TABLE IF EXISTS `department`; CREATE TABL ...

  7. springboot学习随笔(四):Springboot整合mybatis(含generator自动生成代码)

    这章我们将通过springboot整合mybatis来操作数据库 以下内容分为两部分,一部分主要介绍generator自动生成代码,生成model.dao层接口.dao接口对应的sql配置文件 第一部 ...

  8. springboot整合mybatis出现的一些问题

    springboot整合mybatis非常非常的简单,简直简单到发指.但是也有一些坑,这里我会详细的指出会遇到什么问题,并且这些配置的作用 整合mybatis,无疑需要mapper文件,实体类,dao ...

  9. springBoot整合mybatis、jsp 或 HTML

    springBoot整合mybatis.jsp Spring Boot的主要优点: 1:  为所有Spring开发者更快的入门: 2:  开箱即用,提供各种默认配置来简化项目配置: 3:  内嵌式容器 ...

随机推荐

  1. Django ContentType 的使用

    引入 一切优化,最终都是关于需求的优化.本文介绍需求确定之后的数据库表结构设计优化. 程序员应该都知道,编程是数据结构和算法的结合.所谓数据就是用户需要访问和操作的资源,比如购物类App里面的商品,图 ...

  2. 一线大厂Java面试必问的2大类Tomcat调优

    一.前言 最近整理了 Tomcat 调优这块,基本上面试必问,于是就花了点时间去搜集一下 Tomcat 调优都调了些什么,先记录一下调优手段,更多详细的原理和实现以后用到时候再来补充记录,下面就来介绍 ...

  3. 在MSSQL中的简单数据类型递归

    在某些特定的项目需求中,我们需要实现树状数据结构, 由此,我们需要用递归将数据查询出来. WITH T AS ( SELECT ID,PID FROM TableName WHERE ID=1 UNI ...

  4. 弹性盒子中的order

    order order 属性 设置或检索弹性盒模型对象的子元素出现的順序.. 注意:如果元素不是弹性盒对象的元素,则 order 属性不起作用. <!DOCTYPE html> <h ...

  5. SAP QM 检验批里样品数量的确定

    SAP QM 检验批里样品数量的确定 如下的检验批890000045939, 样品数量是50 PC. 检查该检验批对应的检验计划, 这些检验特性都有自己的取样策略,相关的取样数量,体现在结果录入界面, ...

  6. python数据挖掘第三篇-垃圾短信文本分类

    数据挖掘第三篇-文本分类 文本分类总体上包括8个步骤.数据探索分析->数据抽取->文本预处理->分词->去除停用词->文本向量化表示->分类器->模型评估.重 ...

  7. Stm32使用串口空闲中断,基于队列来接收不定长、不定时数据

    串口持续地接收不定长.不定时的数据,把每一帧数据缓存下来且灵活地利用内存空间,下面提供一种方式供参考.原理是利用串口空闲中断和DMA,每当对方发来一帧完整的数据后,串口接收开始空闲,触发中断,在中断处 ...

  8. 如何使用TG Pro for Mac的自定义控制功能完全覆盖系统

    在某些情况下,可能需要完全覆盖系统风扇控制并使用自定义算法.通过将Auto Boost规则的强大功能与覆盖系统功能相结合,可以使用TG Pro.请记住,当风扇模式设置为Auto Boost时,这将完全 ...

  9. 使用 Redis 进行阅读数统计并定时持久化

    之前,统计每篇博文的阅读数的方式是经过筛选去重之后直接更新数据库,并发压力直接传导到数据库,假设1秒有1000个并发请求,传统方案会在1秒内并发进行1000次数据库更新操作. 为了降低数据库的并发压力 ...

  10. Graylog 笔记

    安装 基本上有3种方式,1 yum安装2 rpm安装3 docker安装 yum安装 yum安装,参照官方文档是最好的:http://docs.graylog.org/en/3.0/pages/ins ...