当前位置:主页 > 查看内容

从单体架构到分布式数据持久化,ORM 框架之 Mybatis

发布时间:2021-07-05 00:00| 位朋友查看

简介:本文转载自微信公众号「会点代码的大叔」,作者会点代码的大叔 。转载本文请联系会点代码的大叔公众号。 1前置概念 01 持久化 持久化就是把数据保存到可以永久保存的存储设备中,比如磁盘。 02 JDBC 大多数程序员在学习 Java 的过程中,当学习到 Java 访问数……

本文转载自微信公众号「会点代码的大叔」,作者会点代码的大叔 。转载本文请联系会点代码的大叔公众号

1前置概念

01 持久化

持久化就是把数据保存到可以永久保存的存储设备中,比如磁盘。

02 JDBC

大多数程序员在学习 Java 的过程中,当学习到 Java 访问数据库的时候,一定会先学习 JDBC,它是一种用于执行 SQL 语句的 Java API,为数据库提供统一访问,并把数据“持久化”到数据库中。

我再通俗地解释一下(对 JDBC 有一定了解的同学可以直接跳过):

Sun 公司在 97 年发布 JDK1.1 ,JDBC 就是这个版本中一个重要的技术点,要用 Java 语言连接数据库,正常的思维都是 Sun 公司自己来实现如何连接数据库、如果执行 SQL 语句,但是市场上的数据库太多了,数据库之间的差异也很大,而且 Sun 公司也不可能了解每个数据库的内部细节呐...

于是为了让 Java 代码能更好地与数据库连接,Sun 公司于是制定了一系列的接口,说是接口,其实也就是一套【标准】、一套【规范】,具体代码如何实现由各个数据库厂商来敲代码;所以我们常说的“驱动类”,就是各个厂商的实现类。

所以我们在用 JDBC 连接数据库的时候,第一步需要注册驱动,就是要告诉 JVM 使用的是操作哪个数据库的实现类。

03 ORM

在没有 ORM 框架之前,我们操作数据库需要这样:

我们可以看到使用 JDBC 操作数据库,代码比较繁琐,参数拼写在 SQL 中容易出错,而且可读性比较差,增加了代码维护的难度。

有了 ORM 框架之后,我们操作数据库是这样的:

ORM 框架在 Java 对象和数据库表之间做了一个映射,封装了数据库的访问细节,我们再需要操作数据库语句的时候,直接操作 Java 对象就可以了。

2Spring Boot 集成 MyBatis

Java 常用的 ORM 框架有 Hibernate、MyBatis、JPA 等等,我在后文中在比较这集中框架的优缺点,本章节主要介绍 Spring Boot 项目集成 MyBatis 访问数据库。

Step 1. 添加依赖

  1. <dependency> 
  2.     <groupId>mysql</groupId> 
  3.     <artifactId>mysql-connector-java</artifactId> 
  4. </dependency> 
  5.  
  6. <dependency> 
  7.     <groupId>org.mybatis.spring.boot</groupId> 
  8.     <artifactId>mybatis-spring-boot-starter</artifactId> 
  9.     <version>2.0.1</version> 
  10. </dependency> 

Step 2. 配置数据库链接

在 application.yml 文件中配置数据库相关信息。

  1. #数据源配置 
  2. spring: 
  3.   datasource: 
  4.     #数据库驱动 
  5.     driver-class-name: com.mysql.cj.jdbc.Driver 
  6.     #数据库 url 
  7.     url: jdbc:mysql://127.0.0.1:3306/arch?characterEncoding=UTF-8&serverTimezone=UTC 
  8.     #用户名 
  9.     username: root 
  10.     #密码 
  11.     password: root 

Step 3. 配置数据库链接

在我们本机的数据库上,创建一个用户表,并插入一条数据:

  1. CREATE TABLE IF NOT EXISTS `user`( 
  2.    `id` INT UNSIGNED AUTO_INCREMENT,     
  3.    `userid` VARCHAR(100) NOT NULL
  4.    `username` VARCHAR(100) NOT NULL
  5.    `gender` CHAR(1) NOT NULL
  6.    `age` INT NOT NULL
  7.    PRIMARY KEY ( `id` ) 
  8. )ENGINE=InnoDB DEFAULT CHARSET=utf8; 
  9.  
  10. insert into user(userid ,username, gender, age) values('dashu','大叔','M',18); 

Step 4. 创建 Model 层

通常用于接收数据库中数据的对象,我会单独创建一个 model package,类中的属性我习惯和字段保持相同。

  1. package com.archevolution.chapter4.model; 
  2.  
  3. public class User { 
  4.   private int id;//主键ID,自增长 
  5.   private String userId;//用户ID,或看做登录名 
  6.   private String userName;//用户姓名 
  7.   private String gender;//性别 
  8.   private int age;//年龄 
  9.   //省略 get、set、toString 方法 

Step 5. 创建 Dao 层

通常直接和数据库打交道的代码,我们把它们放在 DAO 层(Data Access Object),数据访问逻辑全都在这里;

我们新建一个 dao package,并在下面新建一个**接口**,注意是接口:

  1. @Mapper 
  2. public interface UserDao { 
  3.   @Select("SELECT id, userId, userName, gender, age FROM USER WHERE id = #{id}"
  4.   public User queryUserById(@Param("id"int id); 

这里我多说几句!

从技术角度来说,model 中的属性可以和表中的字段不一样,比如我们在数据库中增加一个手机号的字段叫做 [mobilephone] :

  1. --增加手机号字段 
  2. ALTER TABLE user ADD mobilephone varchar(15) ; 
  3.  
  4. --更新 userid = 1 数据的手机号 
  5. update user set mobilephone = '13800000000' where userid = '1'

我们在 User.java 中添加一个字段,叫做 [telephone]:

我们自己知道 [telephone] 是和数据库中的 [mobilephone] 对应,但是如何让 Mybatis 知道这两个字段要对应上呢?有几个办法:

01. 在 SQL 语句中控制,对名字不相同的字段起别名:

  1. @Select("SELECT id, userId, userName, gender, age, mobilephone as telephone FROM USER WHERE id = #{id}"
  2. public User queryUserTelById(@Param("id"int id); 

02. 使用 @Results 标签,将属性和字段不相同的设置映射(名称相同的可以不写):

  1. @Select("SELECT id, userId, userName, gender, age, mobilephone FROM USER WHERE id = #{id}"
  2. @Results({ 
  3.   @Result(property = "telephone" , column = "mobilephone")   
  4. }) 
  5. public User queryUserTelById2(@Param("id"int id); 

不过我还是建议大家在写 model 类的时候,属性和表中的字段保持一模一样,这样不仅可以减少代码的复杂程度,还能很大程度地增加代码的可读性,减少出错的可能;

有些同学可能会有疑问,很多项目的数据结构设计的不是那么规范,比如字段名称可能是一个很奇怪的名字,比如 flag01、flag02,如果这样的字段从数据库中查询出来,通过接口返回,那么会不会造成接口的可读性太差?

通常我们不会把 model 中的内容直接包装返回,model 很多的是数据库和 Java 对象的映射,而传输数据的话,通常需要 DTO;我们从数据库中查询出来数据放到 model 中,在接口中返回数据之前,把 model 转换成 DTO,而 DTO 中的属性需要保证其规范性和见名知意。

Step 6. 创建 Service 层

我们的项目现在已经有了 Dao 层,用于访问数据,有 Controller 层,用户提供接口访问,那么 Controller 是否能直接调用 Dao 中的方法呢?最好不要直接调用!

通常我们会创建一个 Service 层,用于存放业务逻辑,这时候完整的调用流程是:

Controller - Service - Dao

创建 Service package 之后,在里面创建一个 UserService :

  1. @Service 
  2. public class UserService { 
  3.   @Autowired 
  4.   UserDao userDao; 
  5.  
  6.   public User queryUserById(int id) { 
  7.     return userDao.queryUserById(id); 
  8.   } 

Step 7. 在 Controller 层增加接口

增加一个接口,通过 userId 查询客户信息,并返回客户信息:

  1. @RequestMapping(value = "/queryUser/{id}"
  2. @ResponseBody 
  3. public String queryUserById(@PathVariable("id"int id){ 
  4.     User user = userService.queryUserById(id); 
  5.     return user == null ? "User is not find" : user.toString() ; 

Step 8. 测试验证

在浏览器或客户端中访问接口进行调试测试,可以查询到客户信息:

  1. http://127.0.0.1:8088/queryUser/1 
  2.  
  3. User [id=1, userId=dashu, userName=大叔, gender=M, age=18, telephone=null

03MyBatis 的其他操作

只给出关键代码,完整代码请参考本章节的项目代码。

01. 新增

  1. @Insert("INSERT INTO USER(userId, userName, gender, age) values" 
  2.         + " (#{userId}, #{userName}, #{gender}, #{age})"
  3. public void insertUser(User user); 

02. 修改

  1. @Update("UPDATE USER SET mobilephone = #{telephone} WHERE id = #{id}"
  2. public void updateUserTel(User user); 

03. 删除

  1. @Delete("DELETE FROM USER WHERE id = #{id}"
  2. public void deleteUserById(@Param("id"int id); 

4代码完善

上面我们就完成了 Spring Boot 和 MyBatis 最简单的集成,可以正常地读取数据库做 CRUD 了,但是因为是最简单的集成,所以有一些细节需要完善一下,比如:

参数都在显示在了 url 中;

直接返回 Object.toString(), 不是很友好;

查询不到数据或发生异常,没有做特殊处理;

下面让我们逐步完善

01. 使用 Json 作为参数发送 Post 请求

如果严格地遵守 Restful 风格,那么需要遵守:

查询:GET /url/xxx

新增:POST /url

修改:PUT /url/xxx

删除:DELETE /url/xxx

在这里我们就单纯地认为把参数写在 url 中,容易一眼就看到我们的参数内容,并且如果参数比较多的时候会造成 url 过长,所以通常我们比较习惯使用 Json 作为参数发送 Post 请求。比如新增 User 的接口可以写成这样:

新增 DTO package 并新建 UserDTO:

  1. //使用了 Josn 作为参数,需要设置 headers = {"content-type=application/json"
  2. //@RequestBody UserDto userDto 可以让 JSON 串自动和 UserDto 绑定和转换 
  3. @RequestMapping(value = "/insertUser2",headers = {"content-type=application/json"}) 
  4. @ResponseBody 
  5. public String insertUser2(@RequestBody UserDto userDto){ 
  6.     //DTO 转成  Model 
  7.     User user = new User(); 
  8.     user.setUserId(userDto.getUserId()); 
  9.     user.setUserName(userDto.getUserName()); 
  10.     user.setGender(userDto.getGender()); 
  11.     user.setAge(userDto.getAge()); 
  12.  
  13.     userService.insertUser(user); 
  14.  
  15.     return "Success" ; 

新增 User 的接口:

  1. //使用了 Josn 作为参数,需要设置 headers = {"content-type=application/json"
  2. //@RequestBody UserDto userDto 可以让 JSON 串自动和 UserDto 绑定和转换 
  3. @RequestMapping(value = "/insertUser2",headers = {"content-type=application/json"}) 
  4. @ResponseBody 
  5. public String insertUser2(@RequestBody UserDto userDto){ 
  6.     //DTO 转成  Model 
  7.     User user = new User(); 
  8.     user.setUserId(userDto.getUserId()); 
  9.     user.setUserName(userDto.getUserName()); 
  10.     user.setGender(userDto.getGender()); 
  11.     user.setAge(userDto.getAge()); 
  12.  
  13.     userService.insertUser(user); 
  14.  
  15.     return "Success" ; 

让我们调用接口测试一下:

  1. {  
  2.   "userId""lisi",  
  3.    "userName""李四",  
  4.    "gender""F",  
  5.    "age""40",  
  6.    "telephone""18600000000" 

02. 规范回参

直接返回 Object.toString(), 不是很友好;

让我们设计一个简单的回参对象,包括 code-状态码,message-异常信息描述,data-数据:

  1. public class JsonResponse { 
  2.   private String code; 
  3.   private String message; 
  4.   private Object data; 
  5.   //省略 set、get 方法 

其中 code 我们就参考 Http 状态码,使用常用的几个:

  1. public class ResponseCode { 
  2.   public static final String SUCCESS = "200";//查询成功 
  3.   public static final String SUCCESS_NULL = "204";//查询成功,但是没有数据 
  4.   public static final String PARAMETERERROR = "400";//参数错误 
  5.   public static final String FAIL = "500";//服务器异常 

这时我们再来重写一下查询接口:

  1. @RequestMapping(value = "/queryUser2"
  2. @ResponseBody 
  3.   public JsonResponse queryUser2ById(@RequestBody UserDto userDto){ 
  4.   JsonResponse res = new JsonResponse(); 
  5.  
  6.   //省略参数校验 
  7.  
  8.   User user = userService.queryUserById(userDto.getUserId()); 
  9.  
  10.   if(user != null){ 
  11.     //能查询到结果,封装到回参中 
  12.     res.setCode(ResponseCode.SUCCESS); 
  13.     res.setData(user);; 
  14.   }else
  15.     //如果查询不到结果,则返回 '204' 
  16.     res.setCode(ResponseCode.SUCCESS_NULL); 
  17.     res.setMessage("查询不到数据"); 
  18.   } 
  19.  
  20.   return res; 

调用结果可以看到封装后的回参,看起来是不是规范了很多:

  1.    "code""200"
  2.    "message"null
  3.    "data": { 
  4.       "id": 3, 
  5.       "userId""lisi"
  6.       "userName""李四"
  7.       "gender""F"
  8.       "age": 40, 
  9.       "telephone"null 
  10.    } 

03. 异常处理

如果代码在运行过程中发生异常,那么改如何处理呢?直接把异常信息返回给前端么?这样做对调用方不是很友好,通常我们把错误日志打印到本地,给调用方返回一个异常状态码即可。

Service、Dao 层的集成都往上抛:

  1. public User queryUserById(int userId) throws Exception{ 
  2.   return userDao.queryUserById(userId); 

在 Controller 层抓住异常,并封装回参:

  1. User user = new User(); 
  2. try { 
  3.   user = userService.queryUserById(userDto.getId()); 
  4. } catch (Exception e) { 
  5.   res.setCode(ResponseCode.FAIL); 
  6.   res.setMessage("服务异常"); 

4MyBatis 常见问题

01. 为什么 MyBatis 被称为半自动 ORM 框架?

有半自动就会有全自动;

Hibernate 就属于全自动 ORM 框架,使用 Hibernate 可以完全根据对象关系模型进行操作,也就是指操作 Java 对象不需要写 SQL,因此是全自动的;而 MyBatis 在关联对象的时候,需要手动编写 SQL 语句,因此被称作“半自动”。

02. 使用注解还是 XML?

相信大部分项目使用 MyBatis 的时候,都是使用 XML 配置 SQL 语句,而我们课程中的例子,都是使用注解的方式,那么这两者有什么区别呢?我们在实际开发中,要如何选择呢?

首先官方是比较推荐使用 XML 的,因为使用注解的方式,拼接动态 SQL 比较费劲儿,如果你们的 SQL 比较复杂,需要多表关联,还是使用 XML 比较好;而且现在也有很多插件,可以自动生成 MyBatis XML。

但是事物总是有两方面的,复杂的 SQL 并不是值得骄傲的事情,如果你们的项目能做到没有复杂 SQL 的话,使用注解会是更好的选择(我们现在的项目 95% 以上的 SQL 都是单表查询)。

03. #{} 和 ${} 的区别是什么?

${} 是字符串替换,#{} 是预编译处理;使用 #{} 可以防止 SQL 注入,提高系统安全性。

04. 如何做批量插入?

注解的方式同样可以使用动态 SQL :

  1. @Insert({ 
  2.     "<script>" 
  3.         + "INSERT INTO USER(userId, userName, gender, age) values" 
  4.         + "<foreach collection='userList' item='item' index='index' separator=','>" 
  5.         + " (#{item.userId}, #{item.userName}, #{item.gender}, #{item.age})" 
  6.         + "</foreach>" 
  7.         + "</script>" 
  8. }) 
  9. public void insertUserList(@Param(value="userList") List<User> userList); 

Spring Boot 集成 MyBatis 做数据库增删查改的操作,是比较基础的知识,希望对初学 Java 的人有所帮助。


本文转载自网络,原文链接:https://mp.weixin.qq.com/s/1spIfekuGn2DVMt5mENZjQ
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐