MyBatis

Mybatis相当于是增强版的JDBC,因为JDBC的开发步骤超过3步了,所以将这些步骤封装起来提高开发效率。

【注意】Mybatis配置文件中不区分大小写

0. 准备工作

新建com.aaron.domain包,其中每一个类对应了数据库的一张表,类的属性与数据表的数据类型要严格一致。

新建com.aaron.dao包,其中为每一个数据表创建一个dao接口,取名规范为【表名+Dao】。

注意dao接口中不要有重载函数

1. 创建mapper文件

在dao包中,为每一个dao接口创建一个同名的xml文件,比如(StudentDao.xml),将一下配置语句粘贴进去(这些配置文件在mybatis官方文档中可找到)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8" ?>

<!--指定约束文件,其作用是检查当前配置文件格式和语法是否符合mybatis要求-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace中的参数可以自定义,按照规范必须为【本dao接口的全限定名称(即包名+类名)】,比如com.aaron.dao.StudentDao-->
<mapper namespace="org.mybatis.example.BlogMapper">
<!--标签名代表执行的操作名称,select标签表示执行select语句,id用于标识该sql语句,方便mybatis定位它,id的值按照规范必须为【接口中使用该sql语句的方法名】-->
<!--resultType代表本sql语句执行后返回结果的java对象类型,比如我们从Student表取数据那么返回类型就是Student,resultType不光写Student,而要写Student类的全限定名称,如com.aaron.domain.Student-->
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>

【获取全限定名称的快捷方法】

2. 创建主配置文件

当前模块的根目录下(与com同级)创建resources文件夹,在其中创建mybatis.xml文件


创建resources文件夹的方法:

先创建一个与com同级的叫resources的package,然后打开project structure


将下列内容复制进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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>
<environments default="development"> <!--environments标签中的每一个双目environment标签代表一个数据的配置(因为开发环境中可能同时用到多个数据库)-->
<environment id="development"> <!--id唯一的标识一个数据库,environments标签default值与该数据id相同,表示默认使用该数据库-->
<transactionManager type="JDBC"/> <!--事务类型,type="JDBC"代表使用jdbc中的Connection对象来做事务处理-->
<dataSource type="POOLED"> <!--pooled代表使用连接池,dataSource双目标签中间就是要连接的数据库的信息-->
<property name="driver" value="${driver}"/> <!--数据库驱动类名,比如value="com.mysql.cj.jdbc.Driver"-->
<property name="url" value="${url}"/> <!--数据库url,比如value="jdbc:mysql://localhost:3306/myDB"-->
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<!--mappers中的mapper双目标签可以有多个,表示有多个mapper文件(项目中有几个mapper文件就要将几个mapper文件信息注册到这里),它们的值就是第1节中创建的mapper文件的路径,比如resource="com/aaron/dao/StudentDao.xml"-->
<mappers>

<mapper resource="org/mybatis/example/BlogMapper.xml"/> <!--注意这里用“/”分隔-->
或者<package name="org.mybatis.dao"> <!--【常用】将该路径下所有xml文件作为mapper加载,注意这里用“.”分隔-->
</mappers>
</configuration>

3. 通过mybatis的SqlSession对象执行sql语句

3.1 查询语句

1
2
3
4
5
6
7
8
9
10
11
12
//1. 读取主配置文件
InputStream in = Resources.getResourceAsStream("mybatis.xml");
//2. 通过sqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3. 从SqlSessionFactory中获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//4. 【重点】指定要执行的sql语句,形式为mapper文档中(namespace+"."+sql标签id)
String sqlId = "com.aaron.dao.Student.getAllStudents";
//5. 【重点】通过sqlId找到配置文件中对应的sql语句并执行
List<Student> studentList = sqlSession.selectList(sqlId);
//6. 关闭SqlSession对象
sqlSession.close();

【注意】每次操作数据库都要创建一个新的sqlSession对象,且sqlSession对象不是线程安全的,因此最好在方法内将sqlSession作为局部变量声明并使用然后关闭,即在方法内部通过factory.openSession()获取到sqlSession并执行完sql语句后在方法内将其close掉。这样就能保证它的使用是线程安全的。

【条件查询】

先加一个mapper

1
2
3
<select id="findAllUser" resultType="com.aaron.domain.user_info">
select * from mydata where username = #{username}
</select>

条件查询代码

1
2
3
4
5
6
7
InputStream in = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = factory.openSession();

String sqlId = "com.aaron.dao.StudentDao.findAllStudent";
user_info user = sqlSession.selectOne(sqlId, new user_info(100,"aaron",123,11)); //传入对象,该对象的属性作为查询条件,即该对象就作用就是提供查询条件的
sqlSession.close();

3.2 更新语句

先去配置文件里把mapper加上

1
2
3
<insert id="insertStudent">
insert into mydata values(#{id}, #{username}, #{password}, #{score})
</insert>

然后修改一下代码

1
2
3
4
5
6
7
8
9
InputStream in = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = factory.openSession(); //可使用有参的openSession(true)获得自动提交事务的sqlSession,下面就不用手动commit()了

String sqlId = "com.aaron.dao.StudentDao.insertStudent"; //【需要修改的地方】
int result = sqlSession.insert(sqlId, new user_info(2,"lisa",123,20)); //【需要修改的地方】
sqlSession.commit(); //【因为mybatis的sqlSession默认不自动提交事务,因此insert,update和delete后要手动提交事务】

sqlSession.close();

4. 配置日志

开启日志后,每次对数据库进行操作都会把操作细节信息打印到控制台上。

将下列语句加入到mybatis主配置文件的configuration标签内

1
2
3
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

5. 包装成工具类

上面用mybatis访问数据库的语句中存在一些重复代码,我们可以将它们进一步封装来简化调用操作。

首先创建与dao和domain平级的包utils,里面新建MyBatisUtils类,然后包装工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyBatisUtils {
static SqlSessionFactory factory = null;
static {
//读取主配置文件
InputStream in = null;
try {
in = Resources.getResourceAsStream("mybatis.xml");
} catch (IOException e) {
e.printStackTrace();
}
//通过sqlSessionFactoryBuilder对象创建SqlSessionFactory对象
factory = new SqlSessionFactoryBuilder().build(in);
}

public static SqlSession getSqlSession() {
//从SqlSessionFactory中获取SqlSession对象
SqlSession sqlSession = null;
if(factory != null){
sqlSession = factory.openSession();
}
return sqlSession;
}
}

这样之前的getAllUserInfo代码就可简化成

1
2
3
4
5
6
SqlSession sqlSession = MyBatisUtils.getSqlSession();
String sqlId = "com.aaron.dao.user_infoDao.getAllUserInfo";
List<user_info> user_infoList = sqlSession.selectList(sqlId);
//action here
sqlSession.close();

6. idea设置文件默认模板

不需要每次创建mybatis主配置文件和mapper文件时都去官方文档中copy一份,第一次配置时顺带把模板配置好就一劳永逸了。

之后就可直接new出模板文件了

7. 再简化

包装了MyBatis工具类后,我们的操作已经非常简便了

1
2
3
4
5
6
SqlSession sqlSession = MyBatisUtils.getSqlSession();
String sqlId = "com.aaron.dao.user_infoDao.getAllUserInfo";
List<user_info> user_infoList = sqlSession.selectList(sqlId);
//action here
sqlSession.close();

其实sqlId是可以通过反射拿到的,MyBatis有函数帮我们实现了这一点,所以上面这个方法可以修改成

1
2
3
SqlSession sqlSession = MyBatisUtils.getSqlSession();
user_infoDao dao = sqlSession.getMapper(user_infoDao.class);
List<user_info> user_infoList = dao.findAllUser();

就相当于我们提供user_infoDao这个接口,MyBatis帮我们创建了一个同名的实现类,实现了user_infoDao接口中所有的函数。

对数据表修改的例子(要手动commit)

1
2
3
4
SqlSession sqlSession = MyBatisUtils.getSqlSession();
user_infoDao dao = sqlSession.getMapper(user_infoDao.class);
int re = dao.addUser(new user_info(22,"tesla",123,80));
sqlSession.commit();

8. 动态sql

可根据条件拼接sql语句(主要是拼接where后的部分),使用动态sql的接口方法传入参数必须为【对象类型】。

8.1 if

1
2
3
4
5
6
7
8
9
10
11
<select id="findAllUser" resultType="com.aaron.domain.user_info">
select * from mydata
where
<if test=" username!=null and username!='' "> <!--判断findAllUser传入的对象参数的username属性-->
username = #{username}
</if>
<if test="score > 0">
or score > #{score}
</if>
</select>

上面语句中,如果username条件不过,语句会报错,因为拼接后的字符串相当于select * from mydata where or score > #{score} ,where可以解决这个问题

8.2 where

解决上面的问题,用where将if括住

1
2
3
4
5
6
7
8
9
10
11
12
<select id="findAllUser" resultType="com.aaron.domain.user_info">
select * from mydata
<where>
<if test=" username!=null and username!='' "> <!--判断findAllUser传入的对象参数的username属性-->
username = #{username}
</if>
<if test="score > 0">
or score > #{score}
</if>
</where>
</select>

这下如果username判断为false也不会报错了,因为where会自动将拼接后无效的语句删除,本例加了where标签后,即使username和score同时判断为false也不会报错,where会将最后拼接的语句修正为select * from mydata

其他还有foreach用于in关键字等,用到再查。

错误处理

Could not find resource com/aaron/dao/xxxDao.xml

在pom.xml中,build标签内插入如下代码

1
2
3
4
5
6
7
8
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>

然后右键,maven,reload project即可。