原文連接:html
MyBatis 官網 是這麼介紹它本身的:java
MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可使用簡單的 XML 或註解來配置和映射原生類型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 對象)爲數據庫中的記錄。mysql
這裏僅展現和 MyBatis 相關的數據庫依賴項,完整的示例,在文末會附上項目代碼連接。git
<!--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
建立 users
表的 SQL:spring
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');
說明:sql
null
值,均可以;column
也能夠不指定;application-dev.properties
:shell
swagger.enable=true server.port=8081 spring.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.Driver mybatis.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
實現自動映射。若是不進行此配置,一般咱們要自定義如下結果集映射:@Results({ @Result(property = "userSex", column = "user_sex"), @Result(property = "nickName", column = "nick_name") }) @Select("SELECT * FROM users WHERE id = #{id}") UserEntity getUserById(Long id);
在不少 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);
這裏僅展現關鍵的部分的代碼,完整可看下文的示例代碼。apache
實體類:
@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; @Override public 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 { @Autowired private UserMapper userMapper; @Override public 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; } @Override public UserEntity getUserById(Long id) { return userMapper.getUserById(id); } @Override public void insert(UserEntity user) { userMapper.insert(user); } @Override public String update(UserEntity user) { userMapper.update(user); return "success"; } @Override public String deleteById(Long id) { userMapper.deleteUserById(id); return "success"; } }
controller 類:
@RestController @RequestMapping("/api/v1/") @Api(tags = {"用戶相關接口"}, value = "用戶模塊") public class UserController { @Autowired private 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
意思同樣,指定返回記錄的數量;上面的分頁結果返回的內容有點多,一些屬性並不想放在返回體中。能夠進一步優化。編寫工具類限定關心的屬性。
分頁查詢結果封裝類:
@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" } ] }
關於 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 注入。${}
將傳入的數據都當成一個字符串,會對自動傳入的數據加一個雙引號。${}
的狀況,order by
、like
語句只能用 ${}
,用 #{}
會多個 ' '
致使 SQL 語句失效。此外動態拼接 SQL,模糊查詢時也要用 ${}
。@ResultMap
比較方便;分頁
PageResult
的優化,參考此文FAQ
歡迎關注我的公衆號 「iPlayMichael」