Springboot整合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 ('大數據');

結果如圖:

ef2d5c1b27fe42ceb8575d6097f8bba2

新建關聯表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)...具體信息如圖:

c1ac47d8ad784064943856b30ebfc36a


這是因爲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測試添加用戶:

c1fe4378e6974a5c9351fb2b297c8b74


6a518a144d064524bab67359001a0c07


接口返回添加“添加成功”字樣,然後去數據庫中查看,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測試刪除用戶:

0617b168664f48d59089f3a38b723590

接口返回添加“刪除成功”字樣,然後去數據庫中查看,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進行測試修改用戶:

78ee36d0d89b4e0d877ea47004749f1f


返回結果沒有問題,再去數據庫查看,數據庫也沒有問題,更新操作完成。

查詢用戶信息

查詢用戶的信息不僅需要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進行測試查詢用戶信息:

5b0d65855a9243bea3fa2b433fed09bb

可以看到這個接口返回了所有的用戶信息,包括用戶的基本信息和角色信息,準確無誤。

總結

Spring和MyBatis實現一對多關聯的增刪改查也有多種方式:可以使用MyBatis來自定義SQL語句來實現;也可以使用Spring的註解結合MyBatis自動生成的代碼來實現。我更喜歡後者,因爲通過Mybatis Generator自動生成代碼以後,這些代碼就不需要再修改了,可以直接封裝service和controller。希望本文對大家有用。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章