Hi I’m Shendi
https://sdpro.top/blog/html/article/1044.html
需求描述
用 SpringBoot 整合 Mybatis 使用久了,再编写没有Spring但需要操作数据库的程序时就会想着使用接口+注解的形式,这样效率比较高和简单
Spring 中只需要编写好接口映射,然后在需要使用的地方用 @Autowirted
注解来自动装载,即可直接使用
没有使用Spring时,使用Mybatis需要用 SqlSessionFactory,然后拿到 SqlSession,再处理操作,操作完再把 SqlSession关闭,非常麻烦。
我原以为 SqlSession.getMapper 拿到接口实例可以一直使用,但是发现关闭连接后,实例就不能再使用了,会报错,于是换了种方法实现需求
下面就介绍下Mybatis的使用过程(接口+注解形式,不列举xml形式的了)
依赖引入
需要两个jar包,一个是jdbc驱动,例如mysql就使用mysql-connector-java.jar,可以从网上找到
还用一个是mybatis的jar包,直接去mybatis的github就可以下载
使用Maven的与SpringBoot引入依赖类似,随便百度下就可以找到引入的依赖,需要引入jdbc和mybatis
Mybatis配置文件编写
格式是xml的,可以参考Mybatis的官方文档,是中文的,文件命名可随意,一般为config.xml或者mybatis-config.xml
Mybatis XML配置文件官方文档
配置文件想放哪放哪,但是一般放到src目录下,因为需要找到这个配置文件,Mybatis提供了一个类
MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。
只要能让程序找到这个文件就可以了(Mybatis提供的Resources类感觉不太好用,只有放到src目录下才能被找到,放到src下的子目录都找不到了…)
我这里把我的配置贴出来了,可以看着更改
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 这里的default指向的是使用的是哪个配置(id) 可以有多个 environment 指向多个数据库 -->
<environments default="devMySql">
<environment id="devMySql">
<!-- 基于jdbc -->
<transactionManager type="JDBC" />
<!-- 数据源 配置一些数据库配置 type="POOLED" 使用MyBatis自带的连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<!-- 数据库 -->
<property name="url" value="jdbc:mysql://localhost:3306/数据库?serverTimezone=UTC&allowMultiQueries=true" />
<property name="username" value="数据库用户名" />
<property name="password" value="数据库密码" />
</dataSource>
</environment>
</environments>
<!-- 使用注解的方式 -->
<mappers>
<mapper class='shendi.TestMapper'/>
</mappers>
</configuration>
从 XML 中构建 SqlSessionFactory
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
SqlSessionFactory包含上面配置文件的信息,并且后续用到的SqlSession就是从这个类实例拿到的
可以理解为是连接池,SqlSession是连接
构建方法如下(官方示例)
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
上面的resource代表了配置文件的路径(经过实际尝试表示出错)
可以把配置文件放到src文件夹下,然后resource直接为配置文件的文件名
编写Mapper接口
对于Sql语句有三种方式,一种是xml的方式,一种是直接接口+注解的方式,还有一种是接口+xml的方式
对我来说好用的当然是直接接口+注解的方式了,简单高效
你只需要编写一个接口,然后使用注解标注在函数上,就可以让函数代表对应的sql操作了,常用的增删改查注解
- @Insert
- @Delete
- @Update
- @Select
这里直接贴出代码了,毕竟非常非常非常简单,例如查询用户根据id
public interface TestMapper {
@Select("SELECT * FROM user WHERE id=${id} LIMIT 1")
HashMap<String,String> userById(int id);
}
上面只是举个例子,还可以使用Bean的方式来获取返回值,而不是HashMap的方式
甚至还可以 if 判断,for循环,递归查询(@Results)等
具体可以去查阅其他文章,本文重点不在这里
需要注意的是,Java在编译过程中,会把参数名改为 arg0,arg1这种,丢失掉原来名称,而Mybatis是通过反射来找参数的,有两种解决办法
1、给参数加 @Param 注解指定名称
@Select("SELECT * FROM user WHERE id=${id} LIMIT 1")
HashMap<String,String> userById(@Param("id") int id);
2、编译时指定parameter参数
javac -parameters 这样编译,但咱当然不可能直接用命令行,一般都是用IDE,例如Eclipse,IDEA这些
- IDEA
- 依次点击 Preferences - Build,Execution,Deployment - Compiler - java Compiler
- 在Additional command line parameters 输入
-parameters
- Eclipse
- Window - prefenrences - java - Compiler
- 勾选 Strore infomation about method parameters(usable via reflection)
本人更偏向第二种
配置映射
有了 Mapper 就需要配置了,在最开始的配置文件编写的那部分有
<!-- 使用注解的方式 -->
<mappers>
<mapper class='shendi.TestMapper'/>
</mappers>
其中 class 代表类路径,TestMapper为映射类,多个就配置多条mapper就好了
我在官网还看到一种,可以将整个包的接口都作为映射类的,但是我使用报错…
<mappers>
<package name="shendi"/>
</mappers>
使用
配置,SQL,工厂,都ok了,最后一步就是使用了,因为太简单,这里直接上代码了
// 拿到工厂
String resource = "config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(inputStream);
// 通过工厂获取SqlSession(连接)
SqlSession ss = ssf.openSession();
// 通过SqlSession获取接口映射实例
TestMapper tm = ss.getMapper(TestMapper.class);
// 直接使用接口就可以了
HashMap<String,String> user = tm.userById(1);
// 最后关闭SqlSession连接
ss.close();
当SqlSession关闭后,getMapper拿到的接口实例就不能在使用了,否则直接报错,所以不能作为类成员这样存起来,这样有点不合理,每次都要拿到SqlSession,使用getMapper,用完关闭。
对于用惯了SpringBoot的我来说太麻烦了,SpringBoot的接口Mapper实例只有一个,也不需要关心拿到连接和关闭连接,于是我就封装了个工具类
工具类(无需关心连接,使用更加简单)
使用动态代理实现,核心代码如下图
先展示如何使用的,像之前获取用户信息只需要一行代码了
HashMap<String,String> user = MapperUtil.TEST.userById(1);
工具类完整代码如下
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
/**
* 所有的映射对象.
* <br>
* @author Shendi <a href='tencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=1711680493'>QQ</a>
*/
public class MapperUtil {
public static final SqlSessionFactory SSF;
// 映射类,全局放出去供调用
public static final TestMapper TEST;
static {
SqlSessionFactory ssf = null;
// 这个地方是找配置文件,用的自己写的,可以根据自己需求改掉
try (FileInputStream fi = new FileInputStream(PathFactory.getPath(PathFactory.PROJECT, "/files/mybatis/config.xml"))) {
ssf = new SqlSessionFactoryBuilder().build(fi);
} catch (IOException e) {
e.printStackTrace();
// 日志,根据自己需求改掉
Log.printErr("SqlSessionFactory创建失败,程序终止: %s", e.getMessage());
System.exit(0);
}
SSF = ssf;
ssf = null;
// 拿到接口实例,后面多一个接口就照着多加一行就可以了
TEST = getMapper(TestMapper.class);
}
// 动态代理,将接口实例的每个函数都代理了,执行前拿到SqlSession,执行完关闭SqlSession
private static <T> T getMapper(Class<T> clazz) {
@SuppressWarnings("unchecked")
T obj = (T) Proxy.newProxyInstance(MapperUtil.class.getClassLoader(), new Class[] {clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try (SqlSession ss = SSF.openSession()) {
T mapper = ss.getMapper(clazz);
Object result = method.invoke(mapper, args);
return result;
}
}
});
return obj;
}
}
补充(重要)
Mybatis的增删改是需要手动调用 SqlSession.commit 才会生效的(事务),所以上面工具类需要稍微改改
如果不commit,不会报错,但是数据库没有数据。
改成如下
private static <T> T getMapper(Class<T> clazz) {
@SuppressWarnings("unchecked")
T obj = (T) Proxy.newProxyInstance(MapperUtil.class.getClassLoader(), new Class[] {clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession ss = SSF.openSession();
try {
T mapper = ss.getMapper(clazz);
Object result = method.invoke(mapper, args);
return result;
} finally {
ss.commit();
ss.close();
}
}
});
return obj;
}
具体在哪commit可以自行封装
END