Spring Boot 實戰 —— MyBatis(註解版)使用方法

MyBatis

原文連接: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

  • id 設置的是自增,當插入數據時,能夠不傳或者傳 null 值,均可以;
  • 插入數據,能夠指定 column 也能夠不指定;

application-dev.propertiesshell

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"
    }
  ]
}

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 中可用參數名將是 ids2collection 也須定義爲 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

待後續補充

FAQ

MyBatis 中 # 和 $ 的區別

  • 簡單說 #{} 是通過預編譯的,是安全的,而 ${} 是未通過預編譯的,僅僅是取變量的值,是非安全的,存在 SQL 注入。${} 將傳入的數據都當成一個字符串,會對自動傳入的數據加一個雙引號。
  • 使用 ${} 的狀況,order bylike 語句只能用 ${},用 #{} 會多個 ' ' 致使 SQL 語句失效。此外動態拼接 SQL,模糊查詢時也要用 ${}

參考

分頁

FAQ

歡迎關注我的公衆號 「iPlayMichael」

iPlayMichael

相關文章
相關標籤/搜索