Spring Boot整合Mybatis完成級聯一對多CRUD操作


在關系型數據庫中,隨處可見表之間的連接,對級聯的表進行增刪改查也是程序員必備的基礎技能。關於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>
  1. 在項目根目錄下面添加自動生成代碼的配置文件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>
  1. 使用Maven生成代碼:
    我使用的是Eclipse,工程目錄右鍵Run as --> Maven build,在Goals中輸入mybatis-generator:generate命令,點擊Run按鈕即可自動生成代碼。

自動生成代碼時遇到的一些坑

  1. 報錯信息: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

  1. 雖然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。希望本文對大家有用。

完整代碼:GitHub地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM