MyBatis 初步
MyBatis 初步
目前我对 MyBatis 的了解不是很深,停留在企业比较常用的”数据库框架”上,系统性的学习要看官方文档。
这篇随笔主要围绕 SpringBoot 中 gradle 环境的搭建来讲,是我从《深入浅出SpringBoot2》中讨的一些知识。
可以跟着文章做一个基础环境的项目。
引入插件
仓库地址:mvnrepository 和 阿里云
因为用的 gradle ,在 build.gradle 中的 dependencies 中加入依赖包:
implementation "org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2"
implementation "org.springframework.boot:spring-boot-starter-web"
implementation "mysql:mysql-connector-java:8.0.29"
创建数据表和插入数据
终端进入 MySQL 中 xxx 数据库,输入 SQL 语句:
CREATE TABLE t_user(
id INT(12) NOT NULL AUTO_INCREMENT,
user_name VARCHAR(60) NOT NULL,
sex INT(3) NOT NULL DEFAULT 1 CHECK (sex in (1,2)),
note VARCHAR(256) NULL,
PRIMARY KEY(id)
);
INSERT INTO t_user(id,user_name,note) VALUES(1,"user_name_1","zhangsan");
创建实体类并设置别名
因为类的全限定名很长,所以使用 @Alias(value = “xxx”) 的方式,一般用来和数据表属性相对的类上(实体类)。
数据表与之对应的实体类如下(自行加入 getter 和 setter ):
package mybatis.pojo;
import mybatis.enumeration.SexEnum;
import org.apache.ibatis.type.Alias;
/**
* @author enrace
* @Alias MyBatis give the class other name.
*/
@Alias(value = "user")
public class User {
private Long id = null;
private String userName = null;
private String note = null;
/**
* The sex is numeration here need use typeHandler to switch.
*/
private SexEnum sex = null;
public User() {}
/** setter 和 getter 方法自行加入即可 **/
}
创建enum 枚举类型和 typeHandler
typeHandler 是 MyBatis 的重要配置之一,用于不同类型的数据进行自定义转换。
我学习了将 Java 中的 enum (枚举类)的实例和数据库的 int 进行转换。
先看看 enum 的使用,enum 的代码如下(自行加入 getter 和 setter ):
package mybatis.enumeration;
/**
* @author enrace
*/
public enum SexEnum {
MALE(1, "男"),
FEMALE(2,"女");
private int id;
private String name;
SexEnum(int id, String name) {
this.id = id;
this.name = name;
}
public static SexEnum getEnumById(int id) {
for (SexEnum sex : SexEnum.values() ) {
if (sex.getId() == id) {
return sex;
}
}
return null;
}
/** setter 和 getter 方法自行加入即可 **/
}
enum 中有两个类型 MALE 和 FEMALE 分别对应 MySQL 中 int 的 1 和 2。
其中的 getEnumById() 的方法是通过接收 int 返回 enum 的实例。
接着说说 typeHandler,这是通过继承的方式自定以了一个 typeHandler:
package mybatis.typehandler;
import mybatis.enumeration.SexEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @MappendJdbcTypes Declare JdbcType to be an integer.
* @author enrace
*/
@MappedJdbcTypes(JdbcType.INTEGER)
@MappedTypes(value = SexEnum.class)
public class SexTypeHandler extends BaseTypeHandler<SexEnum> {
/**
* Read gender by column name.
*/
@Override
public SexEnum getNullableResult(ResultSet rs, String col) throws SQLException {
int sex = rs.getInt(col);
if(sex != 1 && sex != 2) {
return null;
}
return SexEnum.getEnumById(sex);
}
/**
* Read gender by subscript.
* @param rs
* @param idx
* @return SexEnum
* @throws SQLException
*/
@Override
public SexEnum getNullableResult(ResultSet rs, int idx) throws SQLException{
int sex = rs.getInt(idx);
if (sex != 1 && sex != 2) {
return null;
}
return SexEnum.getEnumById(sex);
}
/**
* Read gender from a stored procedure.
* @param cs
* @param idx
* @return SexEnum
* @throws SQLException
*/
@Override
public SexEnum getNullableResult(CallableStatement cs, int idx) throws SQLException {
int sex = cs.getInt(idx);
if (sex != 1 && sex != 2) {
return null;
}
return SexEnum.getEnumById(sex);
}
/**
* Set not null gender parameter.
* @param ps
* @param idx
* @param sex
* @param jdbcType
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int idx, SexEnum sex, JdbcType jdbcType)
throws SQLException {
ps.setInt(idx, sex.getId());
}
}
这个类的几种方法,主要是通过 enum.getEnumById() 返回一个 enum 类型的结果。
我将 SexTypeHandler 分成简述和细述:
简叙:
网上了解的是: 一个 setxxx 方法,表示向 PreparedStatement 里面设置值。三个 getxxx 方法,一个是根据列名获取值,一个是根据列索引位置获取值,最后一个是存储过程。
细述:
ResultSet 类型我去查了一下,表示数据库结果集的数据表,其中的 getXXX 表示在结果集中检索 XXX 类型。
ResultSet.getInt(col) 方法通过 SQL 子句中指定的列的标签获取 sex 的 int。
ResultSet.getInt(idx) 方法通过第几列的方式从数据表中获取 sex 的 int。
CallableStatement 类型可以返回一个对象或多个对象ResultSet。
网上了解到 CallableStatement 类型用于从Java程序调用存储过程,存储过程是我们在数据库中为某些任务编译的一组语句。 当我们处理具有复杂场景的多个表时,存储过程是有益的,而不是向数据库发送多个查询,我们可以将所需的数据发送到存储过程,并在数据库服务器本身中执行逻辑。
自己不是很明白存储过程,不过 CallableStatement 可以获取到结果,自然能获取到我们需要的 int 类型。
cs.getInt(idx) 方法通过传入的 int 检索指定JDBC INT类型的值。
ps.setInt(idx, sex.getId()) 设置给定的 java int
指定参数值。驱动将一个 SQL INTEGER
值发送到数据库。
定义 MyBatis 操作接口
操作接口(Mapper 接口)使用来帮助数据库和 POJO 映射的。
注意: 仅仅为一个接口,不需要任何实现类。
package mybatis.dao;
import mybatis.pojo.User;
import org.springframework.stereotype.Repository;
/**
* @author enrace
*/
@Repository
public interface MyBatisUserDao {
/**
* Get User.
* @param id
* @return User
*/
public User getUser(Long id);
}
@Repository 用于标注数据访问组件,即 DAO 组件。
除了 操作接口还需要创建映射文件,映射文件的 namespace 是与操作接口对应的。
创建映射文件(小坑)和添加配置
映射文件让 POJO (类) 能够与数据库的数据对应,是 xml 类型的。
主要内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.dao.MyBatisUserDao">
<select id="getUser" parameterType="long" resultType="user">
select id, user_name as userName, sex, note from t_user where id = #{id}
</select>
</mapper>
主要的几个属性:
namespace 指定一个接口,就是需要方法需要执行 sql 的接口。
<select> 标签代表一个查询语句。
id 指代这个 SQL,它与接口是同名的(个人认为是映射)。
parameterType 是说明属性配置为 Long (个人理解为传入参数类型)。
resultType 这里指定返回的类型(记得 @Alias 设置的别名就是 user ,那么到时会返回一个 User 实例)。
然后再去 application.properties 添加如下信息:
# 数据库 url
spring.datasource.url = jdbc:mysql://localhost:3306/xxx
# 数据库用户名
spring.datasource.username = zhangsan
# 数据库密码
spring.datasource.password = passwd123
# 最大等待连接中的数量你,设置 0 没有限制
spring.datasource.tomcat.max-idle = 10
# 最大连接活动数
spring.datasource.tomcat.max-active = 50
# 最大等待毫秒数,单位 ms ,超过时间会出错误信息
spring.datasource.tomcat.max-wait = 10000
# 数据库连接池初始化连接数
spring.datasource.tomcat.initial-size = 5
# 映射文件配置
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
# 扫描别名包,和注解 @Alias 联用
mybatis.type-aliases-package=mybatis.pojo
# 配置 typeHandler 的扫描包
mybatis.type-handlers-package=mybatis.typehandler
#logging.level.root = DEBUG
#logging.level.org.springframework = DEBUG
#logging.level.org.org.mybatis = DEBUG
小坑
映射文件有了,我将它放在了项目 mybatis.mapper 包下,但是后面执行报了以下异常:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): mybatis.dao.MyBatisUserDao.getUser
分析过程:
classpath 指的文件目录是什么?
classpath 指的是 build/resources (gradle 构建包) 或是 target/classes (maven 构建包)。
结果:
发现没有 mapper 目录和我的映射文件,手动添加可正常执行。
解决方法一
在 resources 资源目录创建 mybatis 目录和 mapper 子目录,将映射文件放入其中,原因是 resources 中的文件 gradle build 的时候会保留下来。这种是资源文件分离的方式。
解决方法二
build.gradle 中添加下面的代码:
sourceSets {
main {
resources {
srcDirs "src/main/java"
}
}
}
这是资源路径设置,添加代码后 gradle build 的时候不会删除 java 目录下的 非 .java 后缀文件。
使用 MapperFactoryBean 装配 MyBatis 接口
上面的 MyBatisUserDao 是一个 Mapper 接口,不可以使用 new 为其 生成对象实例。需要用到两个类,它 们 是 MapperFactoryBean 和 MapperScannerConfigurer 。其中 MapperFactoryBean 针对接口配置,MapperScannerConfigurer 则是扫描装配。书中提到 @MapperScan 可以后面去使用一下,它更为简便也是用来将对应接口扫描装配到 Spring IoC 容器中的。
接下来我们创建一个 Bean 来配置 MyBatisUserDao 接口,在 @SpringBootApplication 注解文件下增加代码:
@Autowired
SqlSessionFactory sqlSessionFactory = null;
/**
* Define a Mapper interface of MyBatis.
* @return MapperFactoryBean<MyBatisUserDao>
*/
@Bean
public MapperFactoryBean<MyBatisUserDao> initMyBatisUserDao() {
MapperFactoryBean<MyBatisUserDao> bean = new MapperFactoryBean<>();
bean.setMapperInterface(MyBatisUserDao.class);
bean.setSqlSessionFactory(sqlSessionFactory);
return bean;
}
SqlSessionFactory 是 Spring Boot 自动为我们生成的。
开发服务层
由于不是很难理解,直接上代码。
服务接口类代码,如下:
package mybatis.service;
import mybatis.pojo.User;
/**
* @author enrace
*/
public interface MyBatisUserService {
/**
* Get user object.
* @param id
* @return User
*/
public User getUser(Long id);
}
实现类代码,如下:
package mybatis.service.impl;
import mybatis.dao.MyBatisUserDao;
import mybatis.pojo.User;
import mybatis.service.MyBatisUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author enrace
*/
@Service
public class MyBatisUserServiceImpl implements MyBatisUserService {
@Autowired
private MyBatisUserDao myBatisUserDao = null;
@Override
public User getUser(Long id) {
return myBatisUserDao.getUser(id);
}
}
实现类中,通过 @Autowired 自动装配 MyBatisUserDao 的 Bean ,我们就实现了 getUser() 方法,从而可以获得 User 对象。
创建控制器
有了控制器就可以通过 url 测试结果。
控制器代码,如下:
package mybatis.controller;
import mybatis.pojo.User;
import mybatis.service.MyBatisUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author enrace
*/
@RestController
public class MyBatisController {
@Autowired
private MyBatisUserService myBatisUserService = null;
@RequestMapping("/getUser")
public User getUser(Long id) {
return myBatisUserService.getUser(id);
}
}
@RequestMapping(“/getUser”) 设置请求的映射,通过传入 id 得到用户( JSON )格式。
完成
启动 Spring Boot 项目,访问 localhost:8080/getUser?id = 1
至此,您已经了解到了 MyBatis 基本的执行过程,祝:事事无忧,天天无 BUG。