MyBatis快速入门第三讲——dao层开发方法
在实际开发中,我们总归是要开发dao层的,使用MyBatis开发dao层,通常有两个方式,即原始dao开发方式和Mapper接口开发方式。在本文中我会使用MyBatis这个框架开发dao层来一一实现以下功能:
- 根据用户id查询用户信息;
- 根据用户名称模糊查询用户信息列表;
- 添加用户信息。
温馨提示:本文案例代码的编写是建立在前文《MyBatis快速入门第二讲——MyBatis的快速入门》案例基础之上的!
MyBatis常用API的使用范围
在使用MyBatis这个框架开发dao层之前,你应该知道一下MyBatis常用的API。
SqlSessionFactoryBuilder的使用范围
SqlSessionFactoryBuilder用于创建SqlSessionFactory,SqlSessionFactory一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产的,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围(即方法体内局部变量)。
SqlSessionFactory的使用范围
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
SqlSession的使用范围
SqlSession中封装了对数据库的操作,如查询、插入、更新、删除等。通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建的。
SqlSession是一个面向程序员的接口,SqlSession中定义了一些数据库操作方法,所以SqlSession作用是操作数据库,并且SqlSession对象要存储数据库连接、事务和一级缓存结构等。
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它是线程不安全的(多线程访问系统,当多线程同时使用一个SqlSession对象时会造成数据冲突问题)。由于SqlSession对象是线程不安全的,因此它的最佳使用范围是请求或方法范围(也可说为SqlSession的最佳使用场合是在方法体内作为局部变量来使用),绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个SqlSession,记得使用完毕就要关闭它。通常会把这个关闭操作放到finally代码块中以确保每次都能执行该关闭操作。
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
原始dao开发方式
原始dao开发方式需要程序员自己编写dao层接口以及其实现类。首先,我们要在工程的src目录下创建一个com.meimeixia.mybatis.dao包,并在该包下编写一个UserDao接口。
package com.meimeixia.mybatis.dao;
import java.util.List;
import com.meimeixia.mybatis.pojo.User;
/**
* 用户信息持久化接口
* @author liayun
*
*/
public interface UserDao {
/**
* 根据用户ID查询用户信息
* @param id
* @return
*/
User getUserById(Integer id);
/**
* 根据用户名查找用户列表
* @param userName
* @return
*/
List<User> getUserByUserName(String userName);
/**
* 添加用户
* @param user
*/
void insertUser(User user);
}
然后,再在src目录下创建一个com.meimeixia.mybatis.dao.impl包,在该包下编写以上UserDao接口的一个实现类(即UserDaoImpl.java)。
package com.meimeixia.mybatis.dao.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.meimeixia.mybatis.dao.UserDao;
import com.meimeixia.mybatis.pojo.User;
import com.meimeixia.mybatis.utils.SqlSessionFactoryUtils;
/**
* 用户信息持久化实现
* @author liayun
*
*/
public class UserDaoImpl implements UserDao {
@Override
public User getUserById(Integer id) {
SqlSession sqlSession = SqlSessionFactoryUtils.getSqlSessionFactory().openSession();
User user = sqlSession.selectOne("user.getUserById", id);
sqlSession.close();
return user;
}
@Override
public List<User> getUserByUserName(String userName) {
SqlSession sqlSession = SqlSessionFactoryUtils.getSqlSessionFactory().openSession();
List<User> list = sqlSession.selectList("user.getUserByUserName", userName);
sqlSession.close();
return list;
}
@Override
public void insertUser(User user) {
SqlSession sqlSession = SqlSessionFactoryUtils.getSqlSessionFactory().openSession();
sqlSession.insert("user.insertUser", user);
sqlSession.commit();
sqlSession.close();
}
}
其中,有一点需要说明,由于SqlSessionFactory是一个接口,而且接口中定义了openSession的不同重载方法,如下图所示。

所以,UserDaoImpl实现类里面的insertUser方法也可以写成下面这样。
@Override
public void insertUser(User user) {
SqlSession sqlSession = SqlSessionFactoryUtils.getSqlSessionFactory().openSession(true);
sqlSession.insert("user.insertUser", user);
sqlSession.close();
}
接着,创建一个JUnit的单元测试类,即UserDaoTest.java,对UserDao接口进行单元测试。详细步骤如下:
- 在工程下新建一个源码目录,例如test,专门用于存放JUnit的单元测试类。
- 右键UserDao.java文件,在弹出的下拉列表中选中New,再在弹出的下拉列表中选中JUnit Test Case。

- 在弹出的对话框中,修改单元测试类的存放目录为test源码目录以及存放的包名为com.meimeixia.mybatis.test。

- 点击Next按钮,在弹出的对话框中勾选全部测试方法,最后点击Finish完成。

这时,便会产生如下图所示的效果。

紧接着,将以上JUnit的单元测试类(即UserDaoTest.java)的内容修改成下面这个样子。
package com.meimeixia.mybatis.test;
import static org.junit.Assert.*;
import java.util.Date;
import java.util.List;
import org.junit.Test;
import com.meimeixia.mybatis.dao.UserDao;
import com.meimeixia.mybatis.dao.impl.UserDaoImpl;
import com.meimeixia.mybatis.pojo.User;
public class UserDaoTest {
@Test
public void testGetUserById() {
UserDao userDao = new UserDaoImpl();
User user = userDao.getUserById(31);
System.out.println(user);
}
@Test
public void testGetUserByUserName() {
UserDao userDao = new UserDaoImpl();
List<User> list = userDao.getUserByUserName("范");
for (User user : list) {
System.out.println(user);
}
}
@Test
public void testInsertUser() {
UserDao userDao = new UserDaoImpl();
User user = new User();
user.setUsername("开原——范德彪");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("开原市");
userDao.insertUser(user);
}
}
读者可对以上方法一一进行测试,我想是不会有任何问题的,我就不再这里进行测试了(偷懒了)。
原始dao开发方式所带来的问题
从以上UserDaoImpl实现类的代码,可以看出原始dao开发方式存在以下问题:
-
dao层接口实现类的方法中存在着大量的重复代码,这些重复的代码就是模板代码。这些模板代码为:
- 先创建SqlSession
- 再调用SqlSession的方法
- 再提交SqlSession
- 再关闭SqlSession
设想能否将这些代码提取出来,因为这可以大大减轻程序员的工作量。
-
调用SqlSession的数据库操作方法需要指定Statement的id,这里存在硬编码,不便于开发维护。
-
调用SqlSession方法时传入的变量,由于SqlSession方法使用泛型,即使变量类型传入错误,在编译阶段也不会报错,这不利于程序员开发。
于是,下面我将使用mapper代理方法来开发dao层,来解决上面我们所发现的问题。
Mapper动态代理开发方式
开发规范
Mapper动态代理开发方式只需要程序员编写Mapper接口(相当于dao层的接口),由MyBatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体与上边dao层接口的实现类方法体相似。注意:Mapper动态代理开发需要遵循以下规范:
- Mapper.xml文件中的namespace与Mapper接口的类路径相同,即namespace必须是接口的全限定名;
- Mapper接口的方法名和Mapper.xml文件中定义的每个Statement的id相同;
- Mapper接口方法的输入参数类型和Mapper.xml文件中定义的每个SQL的parameterType的类型相同;
- Mapper接口方法的输出参数类型和Mapper.xml文件中定义的每个SQL的resultType的类型相同。
只要遵循了以上四个开发规则,那么我们就可以使用Mapper动态代理开发方式来开发dao层了。
编写Mapper.xml(映射文件)
之前,我们在config源码目录下新建了一个mybatis的普通文件夹,该文件夹专门用于存放映射文件。这里,我们可以在该文件夹下创建一个名为UserMapper.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">
<!--
动态代理开发规则:
1. namespace必须是接口的全路径名
2. 接口的方法名必须与SQL语句的ID一致
3. 接口的入参必须与parameterType类型一致
4. 接口的返回值必须与resultType类型一致
-->
<mapper namespace="com.meimeixia.mybatis.mapper.UserMapper">
<!--
id属性即SQL ID,表示SQL语句的唯一标识
parameterType:入参的数据类型
resultType:返回结果的数据类型
-->
<select id="getUserById" parameterType="int" resultType="com.meimeixia.mybatis.pojo.User">
SELECT
`id`,
`username`,
`birthday`,
`sex`,
`address`
FROM
`user`
WHERE
id = #{id2}
</select>
<!-- 如果查询结果返回的是List集合,那么resultType属性的值只需要设置为List集合中的一个元素的数据类型即可 -->
<select id="getUserByUserName" parameterType="string" resultType="com.meimeixia.mybatis.pojo.User">
SELECT
`id`,
`username`,
`birthday`,
`sex`,
`address`
FROM
`user`
WHERE
`username` LIKE '%${value}%'
</select>
<!--
useGeneratedkeys属性的值设为true(默认为false),表示使用自增。keyProperty属性与之配套使用,通常是对应pojo类中的主键属性,这里是User类中的主键属性
-->
<insert id="insertUser" parameterType="com.meimeixia.mybatis.pojo.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO `user` ( `username`, `birthday`, `sex`, `address` ) VALUE ( #{username}, #{birthday}, #{sex}, #{address} )
</insert>
</mapper>
编写Mapper接口
在工程的src目录下新建一个com.meimeixia.mybatis.mapper包,并在该包下创建一个Mapper接口,即UserMapper.java。
package com.meimeixia.mybatis.mapper;
import java.util.List;
import com.meimeixia.mybatis.pojo.User;
/**
* 用户信息持久化接口
* @author liayun
*
*/
public interface UserMapper {
/**
* 根据用户ID查询用户信息
* @param id
* @return
*/
User getUserById(Integer id);
/**
* 根据用户名查找用户列表
* @param userName
* @return
*/
List<User> getUserByUserName(String userName);
/**
* 添加用户
* @param user
*/
void insertUser(User user);
}
以上接口的定义有如下特点:
- Mapper接口的方法名和Mapper.xml文件中定义的Statement的id相同;
- Mapper接口方法的输入参数类型和Mapper.xml文件中定义的Statement的parameterType的类型相同;
- Mapper接口方法的输出参数类型和Mapper.xml文件中定义的Statement的resultType的类型相同。
加载Mapper.xml映射文件
要想加载Mapper.xml映射文件,得在SqlMapConfig.xml文件添加如下配置。

如此一来,SqlMapConfig.xml文件的整个内容就变成下面这个样子了。
<?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>
<!-- 和spring整合后 environments配置将废除, default属性表示使用哪一个环境? -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="liayun" />
</dataSource>
</environment>
<environment id="test">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="liayun" />
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<!-- 第一种方式:resource属性表示基于classpath查找咱们的映射文件 -->
<mapper resource="mybatis/user.xml" />
<mapper resource="mybatis/UserMapper.xml" />
</mappers>
</configuration>
编写测试程序
编写UserMapper接口的一个单元测试类(即UserMapperTest.java),具体步骤我已在本文中详细说明了。最终,我们要将UserMapperTest这个单元测试类的内容修改成下面这个样子。
package com.meimeixia.mybatis.test;
import static org.junit.Assert.*;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import com.meimeixia.mybatis.mapper.UserMapper;
import com.meimeixia.mybatis.pojo.User;
import com.meimeixia.mybatis.utils.SqlSessionFactoryUtils;
public class UserMapperTest {
@Test
public void testGetUserById() {
SqlSession sqlSession = SqlSessionFactoryUtils.getSqlSessionFactory().openSession();
//获取接口的代理实现类,只不过不需要我们去写了
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(33);
System.out.println(user);
sqlSession.close();
}
@Test
public void testGetUserByUserName() {
SqlSession sqlSession = SqlSessionFactoryUtils.getSqlSessionFactory().openSession();
//获取接口的代理实现类,只不过不需要我们去写了
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> list = userMapper.getUserByUserName("范");
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
@Test
public void testInsertUser() {
SqlSession sqlSession = SqlSessionFactoryUtils.getSqlSessionFactory().openSession();
//获取接口的代理实现类,只不过不需要我们去写了
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("赵子龙");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("广州黄埔刘村");
userMapper.insertUser(user);
sqlSession.commit();
sqlSession.close();
}
}
读者可对以上方法一一进行测试,我想是不会有任何问题的,我就不再这里进行测试了(偷懒了)。
小结
selectOne和selectList
动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据Mapper接口方法的返回值来决定的,如果返回List集合则调用selectList方法,如果返回单个对象则调用selectOne方法。
namespace
MyBatis官方推荐使用Mapper动态代理开发方式来开发dao层,程序员就不用编写Mapper接口实现类了。使用这种开发方式时,输入参数可以使用pojo包装对象或map对象,以保证dao的通用性。
更多推荐

所有评论(0)