在实际开发中,我们总归是要开发dao层的,使用MyBatis开发dao层,通常有两个方式,即原始dao开发方式和Mapper接口开发方式。在本文中我会使用MyBatis这个框架开发dao层来一一实现以下功能:
温馨提示:本文案例代码的编写是建立在前文《MyBatis快速入门第二讲——MyBatis的快速入门》案例基础之上的!
在使用MyBatis这个框架开发dao层之前,你应该知道一下MyBatis常用的API。
SqlSessionFactoryBuilder用于创建SqlSessionFactory,SqlSessionFactory一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产的,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围(即方法体内局部变量)。
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
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层接口以及其实现类。首先,我们要在工程的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接口进行单元测试。详细步骤如下:
紧接着,将以上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);
}
}
读者可对以上方法一一进行测试,我想是不会有任何问题的,我就不再这里进行测试了(偷懒了)。
从以上UserDaoImpl实现类的代码,可以看出原始dao开发方式存在以下问题:
dao层接口实现类的方法中存在着大量的重复代码,这些重复的代码就是模板代码。这些模板代码为:
设想能否将这些代码提取出来,因为这可以大大减轻程序员的工作量。
调用SqlSession的数据库操作方法需要指定Statement的id,这里存在硬编码,不便于开发维护。
调用SqlSession方法时传入的变量,由于SqlSession方法使用泛型,即使变量类型传入错误,在编译阶段也不会报错,这不利于程序员开发。
于是,下面我将使用mapper代理方法来开发dao层,来解决上面我们所发现的问题。
Mapper动态代理开发方式只需要程序员编写Mapper接口(相当于dao层的接口),由MyBatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体与上边dao层接口的实现类方法体相似。注意:Mapper动态代理开发需要遵循以下规范:
只要遵循了以上四个开发规则,那么我们就可以使用Mapper动态代理开发方式来开发dao层了。
之前,我们在config源码目录下新建了一个mybatis的普通文件夹,该文件夹专门用于存放映射文件。这里,我们可以在该文件夹下创建一个名为UserMapper.xml的映射文件,其内容如下:
<mapper namespace="com.meimeixia.mybatis.mapper.UserMapper">
<select id="getUserById" parameterType="int" resultType="com.meimeixia.mybatis.pojo.User">
SELECT
`id`,
`username`,
`birthday`,
`sex`,
`address`
FROM
`user`
WHERE
id = #{id2}
select>
<select id="getUserByUserName" parameterType="string" resultType="com.meimeixia.mybatis.pojo.User">
SELECT
`id`,
`username`,
`birthday`,
`sex`,
`address`
FROM
`user`
WHERE
`username` LIKE '%${value}%'
select>
<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>
在工程的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.xml映射文件,得在SqlMapConfig.xml文件添加如下配置。
如此一来,SqlMapConfig.xml文件的整个内容就变成下面这个样子了。
<configuration>
<environments default="development">
<environment id="development">
<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">
<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>
<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();
}
}
读者可对以上方法一一进行测试,我想是不会有任何问题的,我就不再这里进行测试了(偷懒了)。
动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据Mapper接口方法的返回值来决定的,如果返回List集合则调用selectList方法,如果返回单个对象则调用selectOne方法。
MyBatis官方推荐使用Mapper动态代理开发方式来开发dao层,程序员就不用编写Mapper接口实现类了。使用这种开发方式时,输入参数可以使用pojo包装对象或map对象,以保证dao的通用性。