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

James_Shangguan發表於2019-06-06

在關係型資料庫中,隨處可見表之間的連線,對級聯的表進行增刪改查也是程式設計師必備的基礎技能。關於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 ('大資料');

結果如圖:Spring Boot整合Mybatis完成級聯一對多CRUD操作

新建關聯表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)...具體資訊如圖:
    Spring Boot整合Mybatis完成級聯一對多CRUD操作

這是因為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測試新增使用者:

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

介面返回新增“新增成功”字樣,然後去資料庫中檢視,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測試刪除使用者:
Spring Boot整合Mybatis完成級聯一對多CRUD操作

介面返回新增“刪除成功”字樣,然後去資料庫中檢視,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進行測試修改使用者:
Spring Boot整合Mybatis完成級聯一對多CRUD操作

返回結果沒有問題,再去資料庫檢視,資料庫也沒有問題,更新操作完成。

查詢使用者資訊

查詢使用者的資訊不僅需要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 Boot整合Mybatis完成級聯一對多CRUD操作

可以看到這個介面返回了所有的使用者資訊,包括使用者的基本資訊和角色資訊,準確無誤。

總結

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

完整程式碼:GitHub地址

相關文章