Spring boot整合Mybatis實現(xiàn)級聯(lián)一對多CRUD操作的完整步驟
前言
在關系型數(shù)據(jù)庫中,隨處可見表之間的連接,對級聯(lián)的表進行增刪改查也是程序員必備的基礎技能。關于Spring Boot整合Mybatis在之前已經(jīng)詳細寫過,不熟悉的可以回顧Spring Boot整合Mybatis并完成CRUD操作,這是本文操作的基礎。本文先準備一個測試的數(shù)據(jù)庫,然后使用MyBatis Generator進行部分代碼自動生成,再以一個例子來展示稍微高級點的操作:使用Mybatis完成級聯(lián)一對多的CRUD操作。
數(shù)據(jù)庫準備
數(shù)據(jù)庫用到三張表:user表,role表,user_role表。user表用來存儲用戶的信息;role表用來存儲角色信息;user_role表用來將user和role相關聯(lián),存儲user和role的映射關系,使得一個用戶可以有多個角色,每個角色對應其中的一條記錄。
新建user表
CREATE TABLE user( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20), password VARCHAR(20) )
新建role表并插入數(shù)據(jù)
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 ('大數(shù)據(jù)');
結果如圖:
新建關聯(lián)表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 ,省去最簡單的重復代碼編寫。更多詳細情況可以查看官網(wǎng)。
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" 大數(shù)據(jù)字段,不分表 --> <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> <!--數(shù)據(jù)庫鏈接--> <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> <!-- 數(shù)據(jù)庫表的信息 --> <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連接。根據(jù)MySQL 5.5.45+、5.6.26+和5.7.6+的要求,如果沒有設置顯式選項,則默認情況下必須建立SSL連接。您需要通過設置useSSL=false顯式禁用SSL,或者設置useSSL=true并為服務器證書驗證提供信任存儲。
解決方案:在配置文件application.properties中數(shù)據(jù)庫連接后面加上&useSSL=true。
雖然Maven提示“BUILD SUCCESS”,但是仔細看生成的代碼和數(shù)據(jù)庫是不匹配的,提示代碼被重寫,報錯[WARNING] Table Configuration user matched more than one table (spring_boot..user,mysql..user,webshop..user,jeece-iyangcong..user)...具體信息如圖:
這是因為MySQL 8.0版本驅動將參數(shù)nullCatalogMeansCurrent的默認值由true改為了false,在使用MyBatis Generator生成表對應的xml等時會掃描整個服務器里面的全部數(shù)據(jù)庫中的表,而不是掃描對應數(shù)據(jù)庫的表。
解決方案:在配置文件application.properties中數(shù)據(jù)庫連接后面加上&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 支持編程式事務管理和聲明式事務管理兩種方式。編程式事務指的是通過編碼方式實現(xiàn)事務;聲明式事務基于 AOP,將具體業(yè)務邏輯與事務處理解耦。聲明式事務管理使業(yè)務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多。聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規(guī)則聲明,另一種是基于 @Transactional 注解的方式。本文直接使用@Transactional注解實現(xiàn)事務,因為這種方式操作簡潔,代碼可讀性很高。
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測試添加用戶:
接口返回添加“添加成功”字樣,然后去數(shù)據(jù)庫中查看,user表中多了一條數(shù)據(jù),user_role表中也插入了三條數(shù)據(jù)。顯然,這正是需要的結果,這是一個正確的操作。
刪除用戶
刪除用戶的邏輯和添加用戶的邏輯很相似:第一步根據(jù)前端傳過來的id刪除user表中的記錄;第二步userid刪除user_role表中的記錄;這兩個步驟也是滿足事務特性,也是使用@Transactional注解來實現(xiàn)。
代碼如下:
@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測試刪除用戶:
接口返回添加“刪除成功”字樣,然后去數(shù)據(jù)庫中查看,user表中id為1的記錄被刪除了,user_role表中userid為1的三條記錄也都刪除了。顯然,這正是需要的結果。
修改用戶
修改用戶邏輯:在user表中修改username和password,同時在user_role表中修改用戶和角色的映射記錄。修改用戶和角色映射記錄也就是先按照userid進行刪除記錄,然后再插入新的映射信息。在這里同樣使用@Transactional注解來實現(xiàn)事務。
代碼如下:
@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進行測試的時候,發(fā)現(xiàn)報錯:org.apache.ibatis.binding.BindingException: Parameter 'username' not found.
Available parameters are [0, 1, 2, param3, param1, param2]。
解決辦法:在dao層的Mapper.java代碼的參數(shù)加上@param注解 。例如:
void updateByPrimaryKey(@Param("id")Integer id, @Param("username")String username, @Param("password")String password);
使用PostMan進行測試修改用戶:
返回結果沒有問題,再去數(shù)據(jù)庫查看,數(shù)據(jù)庫也沒有問題,更新操作完成。
查詢用戶信息
查詢用戶的信息不僅需要user表中的用戶信息,還需要user_role表中的用戶角色映射關系,還需要role表的角色信息。這也是需要表之間聯(lián)合的操作。
本文采用的方法是新建一個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實現(xiàn)一對多關聯(lián)的增刪改查也有多種方式:可以使用MyBatis來自定義SQL語句來實現(xiàn);也可以使用Spring的注解結合MyBatis自動生成的代碼來實現(xiàn)。我更喜歡后者,因為通過Mybatis Generator自動生成代碼以后,這些代碼就不需要再修改了,可以直接封裝service和controller。希望本文對大家有用。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。
相關文章
Spring占位符Placeholder的實現(xiàn)原理解析
這篇文章主要介紹了Spring占位符Placeholder的實現(xiàn)原理,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03SpringBoot項目實戰(zhàn)之數(shù)據(jù)交互篇
這篇文章主要給大家介紹了關于SpringBoot項目實戰(zhàn)之數(shù)據(jù)交互篇的相關資料,文中通過實例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2022-03-03淺談Java中的final關鍵字與C#中的const, readonly關鍵字
下面小編就為大家?guī)硪黄獪\談Java中的final關鍵字與C#中的const, readonly關鍵字。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10SpringBoot?Loki安裝簡介及實戰(zhàn)思路
這篇文章主要為大家介紹了SpringBoot?Loki安裝簡介及實戰(zhàn)思路詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪的相關資料2022-11-11SpringBoot+Redis執(zhí)行l(wèi)ua腳本的5種方式總結
Lua是一種快速、輕量級的腳本語言,廣泛應用于各種領域,包括數(shù)據(jù)庫,Redis作為一個內嵌Lua解釋器的NoSQL數(shù)據(jù)庫,允許通過Lua腳本在服務器端執(zhí)行一些復雜的操作,本文給大家介紹了使用SpringBoot Redis執(zhí)行l(wèi)ua腳本的五種方式,需要的朋友可以參考下2023-11-11