javascript
Spring Boot 实战 —— MyBatis(注解版)使用方法
原文鏈接:
- Spring Boot 實戰 —— MyBatis(注解版)使用方法
簡介
MyBatis 官網 是這么介紹它自己的:
MyBatis 是一款優秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生類型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 對象)為數據庫中的記錄。
示例代碼
- awesome-spring-boot-examples
依賴
這里僅展示和 MyBatis 相關的數據庫依賴項,完整的示例,在文末會附上項目代碼鏈接。
<!--mysql--> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId> </dependency> <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.0</version> </dependency>spring-boot-start 系列的包,真是給 Spring Boot 開發帶來了極大的便利,它的項目地址是:
- Github-mybatis/spring-boot-starter
- 官宣-What is MyBatis-Spring-Boot-Starter? 推薦
配置
創建 users 表的 SQL:
SET FOREIGN_KEY_CHECKS=0;-- ---------------------------- -- Table structure for `users` -- ---------------------------- DROP TABLE IF EXISTS `users`; CREATE TABLE `users` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',`userName` varchar(32) DEFAULT NULL COMMENT '用戶名',`passWord` varchar(32) DEFAULT NULL COMMENT '密碼',`user_sex` varchar(32) DEFAULT NULL,`nick_name` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;-- ---------------------------- -- Records of sys_user -- ---------------------------- INSERT INTO `users` VALUES (1,'michael翔', '123', 'MAN', 'zx'); INSERT INTO `users` VALUES (2,'張小敬', '123', 'MAN', 'zxj'); INSERT INTO `users` VALUES (3,'李司辰', '123', 'MAN', 'lsc'); INSERT INTO `users` VALUES (4,'崔器', '123', 'MAN', 'cq'); INSERT INTO `users` VALUES (5,'姚汝能', '123', 'MAN', 'yrn'); INSERT INTO `users` VALUES (null,'檀棋', '123', ' WOMAN', 'tq'); INSERT INTO `users` (`userName`,`passWord`,`user_sex`,`nick_name`) VALUES ('michael', '123', 'MAN', 'zx');說明:
- id 設置的是自增,當插入數據時,可以不傳或者傳 null 值,都可以;
- 插入數據,可以指定 column 也可以不指定;
application-dev.properties:
swagger.enable=true server.port=8081spring.datasource.url=jdbc:mysql://192.168.3.43:3306/beta?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Drivermybatis.type-aliases-package==com.michael.springbootmybatis.model mybatis.configuration.map-underscore-to-camel-case=true- 配置駝峰屬性自動映射,例如實體中屬性為 userSex,數據庫屬性為 user_sex,MyBatis 默認是不能自動轉換的。我們可以配置 mybatis.configuration.map-underscore-to-camel-case 實現自動映射。如果不進行此配置,通常我們要自定義以下結果集映射:
在很多 Select 語句需要做結果映射時,自然是相當麻煩。除了上面配置「駝峰屬性自動映射」,也可以用在 @Results 中使用 id 來標識一個映射關系,然后可以用 @ResultMap 復用這個映射關系:
@Select("SELECT * FROM users") @Results(id = "user", value = {@Result(property = "userSex", column = "user_sex"),@Result(property = "nickName", column = "nick_name") }) @Select("SELECT * FROM users WHERE id = #{id}") List<UserEntity> getAll();@ResultMap("user") @Select("SELECT * FROM users WHERE id = #{id}") UserEntity getUserById(Integer id);代碼
這里僅展示關鍵的部分的代碼,完整可看下文的示例代碼。
實體類:
@Data @ApiModel(description = "UserEntity 實體類") public class UserEntity implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "用戶 id", dataType = "Long")private Long id;@ApiModelProperty(value = "用戶名", required = true)private String userName;@ApiModelProperty(value = "密碼")private String passWord;@ApiModelProperty(value = "性別")private UserSexEnum userSex;@ApiModelProperty(value = "昵稱")private String nickName;@Overridepublic String toString() {return "userName " + this.userName + ", password " + this.passWord + " , sex " + this.userSex;}}dao/mapper 接口,數據庫交互(Data Access Object)層:
public interface UserMapper { // @Results({ // @Result(property = "userSex", column = "user_sex", javaType = UserSexEnum.class), // @Result(property = "nickName", column = "nick_name") // })@Select("SELECT * FROM users")Page<UserEntity> getAll();// @Results({ // @Result(property = "userSex", column = "user_sex"), // @Result(property = "nickName", column = "nick_name") // })@Select("SELECT * FROM users WHERE id = #{id}")UserEntity getUserById(Long id);@Insert("INSERT INTO users(userName, passWord, user_sex, nick_name) " +"VALUES(#{userName}, #{passWord}, #{userSex}, #{nickName})")@Options(useGeneratedKeys = true, keyProperty = "id") // @SelectKey(statement = "select last_insert_id()", keyProperty = "id", before = false, resultType = Integer.class)void insert(UserEntity user);@Update("UPDATE users SET userName=#{userName},nick_name=#{nickName} WHERE id = #{id}")void update(UserEntity user);@Delete("DELETE FROM users WHERE id= #{id}")void deleteUserById(Long id); }說明:
- insert 這里用了一個 @Options 的注解,實現了「主鍵回填」的功能,也就是說,再創建好一個 user 之后,user 請求體中的 id 屬性會自動賦值好;
- @SelectKey 注解被注釋掉了,這個注解也同樣可以實現「主鍵回填」的功能;
service 接口:
public interface UserService {/*** 查詢所有用戶** @return*/Map<String,Object> getAll(int pageNum, int pageSize);/*** 根據用戶 ID 查詢用戶** @param id 用戶 ID* @return*/UserEntity getUserById(Long id);/*** 新增一個用戶** @param user*/void insert(UserEntity user);/*** 更新用戶信息,用戶 ID 不傳,會更新失敗** @param user*/String update(UserEntity user);/*** 根據用戶 ID 刪除用戶** @param id*/String deleteById(Long id);}service 接口的實現類:
@Service public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic Map<String,Object> getAll(int pageNum, int pageSize) {//將參數傳給這個方法就可以實現物理分頁了,非常簡單。PageHelper.startPage(pageNum, pageSize);PageInfo<UserEntity> pageInfo = new PageInfo<>(userMapper.getAll());Long total = pageInfo.getTotal();List<UserEntity> users = pageInfo.getList();Map<String,Object> map = new HashMap<>();map.put("total", total);map.put("data", users);return map;}@Overridepublic UserEntity getUserById(Long id) {return userMapper.getUserById(id);}@Overridepublic void insert(UserEntity user) {userMapper.insert(user);}@Overridepublic String update(UserEntity user) {userMapper.update(user);return "success";}@Overridepublic String deleteById(Long id) {userMapper.deleteUserById(id);return "success";}}controller 類:
@RestController @RequestMapping("/api/v1/") @Api(tags = {"用戶相關接口"}, value = "用戶模塊") public class UserController {@Autowiredprivate UserService userService;/*** 查詢全部用戶** @return*/@ApiOperation(value = "獲取用戶列表", notes = "獲取全部用戶信息")@RequestMapping(value = "/users", method = RequestMethod.GET)public Map<String,Object> getUsers(@RequestParam(name = "pageNum", defaultValue = "1", required = false) int pageNum,@RequestParam(name = "pateSize", defaultValue = "2", required = false) int pageSize) {return userService.getAll(pageNum, pageSize);}/*** 根據用戶 ID 查詢用戶** @param id* @return*/@ApiOperation(value = "查詢單用戶", notes = "根據用戶id 查詢其信息")@ApiImplicitParam(name = "id", value = "用戶id", paramType = "query", required = true)@GetMapping("/user/{id}")public UserEntity getUser(Long id) {UserEntity user = userService.getUserById(id);return user;}/*** 存儲用戶信息** @param user*/@ApiOperation(value = "存儲用戶信息", notes = "存儲用戶詳細信息")@RequestMapping(value = "/user", method = RequestMethod.POST)public String save(UserEntity user) {userService.insert(user);// 用到了 主鍵回填 的配置return "Create success, user id: " + user.getId();}/*** 更新用戶信息** @param user*/@ApiOperation(value = "更新用戶信息", notes = "更新用戶的個人信息")@PutMapping("/user/")public void update(@RequestBody UserEntity user) {userService.update(user);}/*** 根據用戶 ID 刪除用戶** @param id*/@ApiOperation(value = "刪除用戶", notes = "根據用戶id刪除用戶信息")@ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用戶id", required = true, paramType = "path")})@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)public void delete(@PathVariable("id") Long id) {userService.deleteById(id);} }啟動類:
@SpringBootApplication @MapperScan("com.michael.springbootmybatis.mapper") public class SpringBootMybatisApplication {public static void main(String[] args) {SpringApplication.run(SpringBootMybatisApplication.class, args);}}- @MapperScan("com.winter.mapper") 這個注解非常的關鍵,這個對應了項目中 mapper/dao 所對應的包路徑。
- 如果不用上面的方式,就需要在每個 mapper/dao 類上使用 @Mapper 注解;
分頁
通常,在進行查詢時,我們為了避免一次性返回所有結果,通常會進行分頁。比如查詢所有用戶的接口,實際應用中,用戶數據可能會很多,如果全部一次返回,明顯不合適。這時候,就需要進行分頁查詢。
本文我們選用插鍵 pagehelper-spring-boot-starter 要進行分頁。
添加依賴
<!-- 分頁插件 --> <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.2.5</version> </dependency>分頁配置
需要添加相應的配置:
#pagehelper分頁插件 pagehelper.helperDialect=mysql pagehelper.reasonable=true pagehelper.supportMethodsArguments=true pagehelper.params=count=countSql pagehelper.row-bounds-with-count=true pageSizeZero=true分頁插鍵參數介紹:
- helperDialect:分頁插件會自動檢測當前的數據庫鏈接,自動選擇合適的分頁方式
- reasonable:分頁合理化參數,默認值為 false。當該參數設置為 true 時,pageNum<=0 時會查詢第一頁, pageNum>pages(超過總數時),會查詢最后一頁。默認 false 時,直接根據參數進行查詢
- params:為了支持 startPage(Object params) 方法,增加了該參數來配置參數映射,用于從對象中根據屬性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默認值, 默認值為 pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。
- supportMethodsArguments:支持通過 Mapper 接口參數來傳遞分頁參數,默認值 false,分頁插件會從查詢方法的參數值中,自動根據上面 params 配置的字段中取值,查找到合適的值時就會自動分頁
- pageSizeZero:默認值為 false,當該參數設置為 true 時,如果 pageSize=0 或者 RowBounds.limit = 0 就會查詢出全部的結果(相當于沒有執行分頁查詢,但是返回結果仍然是 Page 類型)。我測試時,發現不設置,pageSize=0 也會返回全部;
代碼變動
mapper 中查找全部用戶的方法改成如下:
@Select("SELECT * FROM users") Page<UserEntity> getAll();service 接口和其實現類的方法改成:
PageInfo<UserEntity> getAll(int pageNum, int pageSize);service 接口實現類:
@Override public PageInfo<UserEntity> getAll(int pageNum, int pageSize) {//將參數傳給這個方法就可以實現物理分頁了,非常簡單。PageHelper.startPage(pageNum, pageSize);PageInfo<UserEntity> pageInfo = new PageInfo<>(userMapper.getAll());return pageInfo; }注意點:
- PageHelper.startPage(pageNo,pageSize); 只對其后的第一個查詢有效;
controller 類:
@ApiOperation(value = "獲取用戶列表", notes = "獲取全部用戶信息") @RequestMapping(value = "/users", method = RequestMethod.GET) public PageInfo<UserEntity> getUsers(@RequestParam(name = "pageNum", defaultValue = "1", required = false) int pageNum,@RequestParam(name = "pateSize", defaultValue = "2", required = false) int pageSize) {return userService.getAll(pageNum, pageSize); }除了上面的參數名,還習慣用下面的參數名:
- offset:和 pageNum 意思一樣,指定返回記錄的開始位置;
- limit:和 pageSize 意思一樣,指定返回記錄的數量;
進一步
上面的分頁結果返回的內容有點多,一些屬性并不想放在返回體中??梢赃M一步優化。編寫工具類限定關心的屬性。
分頁查詢結果封裝類:
@Data public class PageResult {/*** 當前頁碼*/private int pageNum;/*** 每頁數量*/private int pageSize;/*** 記錄總數*/private long totalSize;/*** 頁碼總數*/private int totalPages;/*** 數據模型*/private List<?> content; }分頁查詢工具類:
public class PageUitls {/*** 將分頁信息封裝到統一的接口** @param pageInfo* @return*/public static PageResult getPageResult(PageInfo<?> pageInfo) {PageResult pageResult = new PageResult();pageResult.setPageNum(pageInfo.getPageNum());pageResult.setPageSize(pageInfo.getPageSize());pageResult.setTotalSize(pageInfo.getTotal());pageResult.setTotalPages(pageInfo.getPages());pageResult.setContent(pageInfo.getList());return pageResult;} }接口方法:
/*** 查詢所有用戶** @return*/ PageResult getAll(int pageNum, int pageSize);接口實現類:
@Override public PageResult getAll(int pageNum, int pageSize) {//將參數傳給這個方法就可以實現物理分頁了,非常簡單。PageHelper.startPage(pageNum, pageSize);List<UserEntity> users = userMapper.getAll();PageInfo<UserEntity> pageInfo = new PageInfo<>(users);return PageUitls.getPageResult(pageInfo); }這樣改寫后,返回體就簡潔許多了:
{"pageNum": 1,"pageSize": 2,"totalSize": 3,"totalPages": 2,"content": [{"id": 1,"userName": "Michael翔","passWord": "123","userSex": "MAN","nickName": "ZX"},{"id": 2,"userName": "HQH","passWord": "123","userSex": "WOMAN","nickName": "QQ"}] }IN 查詢
關于 MyBatis 的 IN 查詢,也是試驗了很久,才 OK 的。 StackOverflow 上就有類似的問題 How to use Annotations with iBatis (myBatis) for an IN query?。
為了測試 MyBatis IN 查詢,我們將之前的根據 ID 查詢用戶信息的接口進行修改,讓它支持根據輸入的 ID 列表查詢多用戶信息。
controller:
@ApiOperation(value = "查詢指定 ID 的用戶", notes = "根據用戶 id 列表查詢其信息") @ApiImplicitParam(name = "ids", value = "用戶 id 列表", paramType = "path", required = true) @GetMapping(value = "/user/{ids}") public PageResult getUser(@RequestParam(name = "pageNum", defaultValue = "1", required = false) int pageNum,@RequestParam(name = "pageSize", defaultValue = "2", required = false) int pageSize,@PathVariable String ids) {List<String> idLst = Arrays.asList(ids.split(","));PageResult user = userService.getUserById(pageNum, pageSize, idLst);return user; }這里有個小注意點,@ApiImplicitParam 注解中的 paramType = "path" 記得修改為 path,因為請求參數中包含路徑變量了,否則渲染 URL 時,會出問題。
mapper 類:
@Select({"<script>","SELECT * ","FROM users WHERE id IN","<foreach item='id' index='index' collection='ids' open='(' separator=',' close=')'>","#{id}","</foreach>","</script>" }) List<UserEntity> getUserById(@Param("ids") List<String> ids);說明:
- item 標識集合中每一個元素進行迭代是的別名,很多教程中設置為 item,我這里改為 id 也是 OK,而且也易于理解;
- index 指定一個名字,用于表示在迭代過程中,每次迭代到的位置,從 0 開始;
- open 表示該語句以什么開始;
- separator 表示在每次進行迭代之間以什么符號作為分隔符;
- close 表示以什么結束;
- collection 屬性,該屬性是必須指定的,但是在不同情況下,該屬性的值是不一樣的;
- @Param 的設置比較關鍵,相當于給其修飾的參數指定一個別名:
- 使用 @Param,默認會和參數名同名,或者以注解傳入的變量名為準。變量名將作為 @Select 中的可用參數,比如,我這里這樣定義 @Param("ids2") List<String> ids,那么,@Select 中可用參數名將是 ids2,collection 也須定義為 ids2,否則會報錯:nested exception is org.apache.ibatis.binding.BindingException: Parameter 'list' not found. Available parameters are [ids2, param1];
- 不使用 @Param 時,那么,此時 collection 需要定義為 list,否則會報錯:nested exception is org.apache.ibatis.binding.BindingException: Parameter 'list2' not found. Available parameters are [collection, list];
上面的說明參考自 mybatis查詢sql中in條件使用(foreach) ,沒有找到官方文檔支撐,待補充。
動態 SQL
待后續補充
- spring boot(8)-mybatis三種動態sql
- MyBatis注解應用之動態SQL語句
FAQ
MyBatis 中 # 和 $ 的區別
- 簡單說 #{} 是經過預編譯的,是安全的,而 ${} 是未經過預編譯的,僅僅是取變量的值,是非安全的,存在 SQL 注入。${} 將傳入的數據都當成一個字符串,會對自動傳入的數據加一個雙引號。
- 使用 ${} 的情況,order by、like 語句只能用 ${},用 #{} 會多個 ' ' 導致 SQL 語句失效。此外動態拼接 SQL,模糊查詢時也要用 ${}。
參考
- 純潔的微笑-Spring Boot(六):如何優雅的使用 Mybatis 本文的主要參考文章之一,入門挺好
- CSDN-larger5-[增刪改查] SpringBoot + MyBatis(注解版) 這位博主的示例,代碼結構和風格都比較規范,值得學習
- CSDN-LuisChen的博客-Spring boot Mybatis 整合(完整版)
- 博客園-Ruthless-SpringBoot+Mybatis+Pagehelper分頁 查詢結果關系映射那塊,該文章介紹的 @ResultMap 比較方便;
分頁
- CSDN-SpringBoot使用Mybatis注解開發教程-分頁-動態sql 分頁參考
- 博客園-朝雨憶輕塵-Spring Boot:實現MyBatis分頁 推薦,PageResult 的優化,參考此文
- PageHelper-官宣-如何使用分頁插件
FAQ
- 知乎——這也許是你不曾留意過的 Mybatis 細節
歡迎關注個人公眾號 「iPlayMichael」
轉載于:https://www.cnblogs.com/michael-xiang/p/11221743.html
總結
以上是生活随笔為你收集整理的Spring Boot 实战 —— MyBatis(注解版)使用方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大一暑假第三周学习体会
- 下一篇: Visual Studio2005入门.