使用Mybatis开发Dao,通常有两种方法:原始Dao开发方法和Mapper接口开发方法
SqlSession的使用范围
SqlSession中封装了对数据库的操作:查询、插入、更新、删除等
通过SQLSessionFactory创建SqlSession,而SqlSessionFactory是通过SQLSessionFactoryBuilder进行创建
- SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于创建SqlSessionFactory,SqlSessionFactory一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory产生,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量
- SqlSessionFactory
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory.
- SqlSession
SqlSession是一个面向用户的接口,SqlSession中定义了数据库操作方法。
每个线程都应该有自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围,不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个SqlSession,使用完后要关闭它。通常把关闭操作放在finally块以确保每次都能执行关闭。
SqlSession session = SqlSessionFactory.openSesion();
try{
//do work
}finally{
session.close();
}
原生Dao实现(编写Dao接口和Dao实现类)
- 映射文件
<?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">
<!--namespace 命名空间,做SQL隔离-->
<!--用户的增删改查的SQL语句在代码中通过id进行调用,如果表单特别多,每张表单的SQL语句特别多,id可能重复-->
<!--namespace 相当于又加了一层,调用的时候通过namespace加上id进行调用,避免重名的可能-->
<!--namespace起名是有规范的-->
<mapper namespace="test">
<!--具有增删改查对应的子标签-->
<!--id : sql语句唯一标识-->
<!--parameterType:指定传入参数类型,是javaBean中对应属性的类型-->
<!--resultType : 返回结果集类型,如果返回结果为集合,可以调用selectList()方法,这个方法返回的结果就是一个集合,所以映射文件中应该配置成集合泛型的类型-->
<select id="findUserById" parameterType="java.lang.Integer" resultType="cn.zst.domain.User">
<!--#{}占位符,起到占用的作用,如果传入的是基本类型(String long double int Boolean float等)那么 # {}中的变量名称可以随意写-->
SELECT * from `user` WHERE id = #{id}
</select>
<select id ="findUserByName" parameterType="java.lang.String" resultType="cn.zst.domain.User">
<!-- SELECT * from `user` WHERE username like #{name}-->
<!--${}拼接符,字符串原样拼接,如果传入的参数是基本类型(String long double int boolean float等)那么${}中的变量名称必须是value-->
<!--注意:拼接符有SQL注入的风险,所以慎重使用-->
SELECT * from `user` WHERE username like '%${value}%'
</select>
<!--增加-->
<!--如果业务需要返回数据库自增主键,可以使用SELECT LAST_INSERT_ID()-->
<insert id="insertUser" parameterType="cn.zst.domain.User">
<!--执行SELECT LAST_INSERT_ID()数据库函数,返回自增的主键-->
<!--keyProperty:将返回的主键放入传入参数的id中保存,此处的传入参数为User ,-->
<!--order:当前函数相对于insert语句的执行顺序,在insert前执行是before,在insert后执行是after-->
<!--resultType:id 类型 ,也就是在keyproperties中属性的类型-->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
<!--如果传入的是JavaBean类型,那么#{}中的变量名称必须是JavaBean中对应的属性.属性.属性.属性.属性-->
<!--例如,User中有一个Customer的属性,customer有一个custname属性,则表示为 customer.custname-->
INSERT into `user` (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
<!--删除用户-->
<delete id="delUserById" parameterType="java.lang.Integer">
delete from `user` WHERE id=#{id}
</delete>
<!--更新,根据id来跟新,因为此处传入的是两个参数,所有通过user对象传入,因为它包含所有需要的属性-->
<update id="updateUserById" parameterType="cn.zst.domain.User" >
UPDATE `user` set username=#{username} WHERE id=#{id}
</update>
</mapper>
- Dao接口
public interface UserDao {
public User findUserByID(Integer id);
public List<User> findUserByUserName(String userName);
}
public class UserDaoImpl implements UserDao {
private SqlSessionFactory sqlSessionFactory;
//通过构造方法注入
public UserDaoImpl(SqlSessionFactory sqlSessionFactory){
this.sqlSessionFactory=sqlSessionFactory;
}
@Override
public User findUserByID(Integer id) {
//SQLSession是线程不安全的,所以它的最佳使用范围是方法体内
SqlSession openSession = sqlSessionFactory.openSession();
User user = openSession.selectOne("test.findUserById", id);
return user;
}
@Override
public List<User> findUserByUserName(String userName) {
SqlSession openSession = sqlSessionFactory.openSession();
List<User> list = openSession.selectList("test.findUserByName", userName);
return list;
}
}
- 测试类
public class UserTest {
@Test
public void testFindUserById() throws Exception{
String resource="SqlMapConfig.xml";
//通过流将核心配置文件读取出来
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过核心配置文件输入流来创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂创建会话
SqlSession openSession = factory.openSession();
//第一个参数,所调用的SQL语句 = namespace+.+sql的id
User user = openSession.selectOne("test.findUserById", 1);
System.out.println(user);
openSession.close();
}
@Test
public void testFindUserByName() throws Exception{
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession openSession = factory.openSession();
List<User> list = openSession.selectList("test.findUserByName", "王");
System.out.println(list);
openSession.close();
}
@Test
public void testInsertUser() throws Exception{
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession openSession = factory.openSession();
User user = new User();
user.setUsername("王名");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("河南郑州");
openSession.insert("test.insertUser",user);
//提交事务
//如果不提交事务,则数据不能插入到数据库中
//在Hibernate中如果要提交事务,首先要开启事务,beganTransation
//此处Mybatis会自动开启事务,但不知道要什么时候提交事务。所以需要手动提交事务
openSession.commit();
//自增主键的id
System.out.println(user.getId());
}
@Test
public void testDelUserById () throws Exception{
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession openSession = factory.openSession();
openSession.delete("test.delUserById",28);
//提交事务
openSession.commit();
}
@Test
public void testUpdateUserById() throws Exception{
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession openSession = factory.openSession();
User user = new User();
user.setUsername("王志");
user.setId(24);
openSession.update("test.updateUserById",user);
openSession.commit();
}
}
原生方式实现Dao存在的问题
- Dao 方法体中存在重复代码:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法
- 调用SqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护。
代理Dao实现
动态代理开发规范
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法同上边Dao接口实现类方法.Mapper接口开发需要遵循以下规范:
- Mapper.xml文件中的namespace与mapper接口的类路径相同
- Mapper接口方法名与Mapper.xml中定义的每个statement和id相同
- Mapper接口方法的输入参数类型和Mapper.xml中定义的每个sql的parameterType的类型不同
- Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
映射文件(Mapper.xml)
定义mapper映射文件UserMapper.xml(内容同User.xml),需要修改namespace的值为UserMapper接口路径。UserMapper.xml在classpath下mappe目录下。
<?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接口代理实现编写规则:
1 映射文件中namespace要等于接口的全路径名称
2 映射文件中SQL语句id要等于接口的方法名称
3 映射文件中传入参数类型要等于接口方法的传入参数类型
4 映射文件中返回结果集类型要等于接口的返回值类型
-->
<mapper namespace="cn.zst.mapper.UserMapper">
<select id="findUserById" parameterType="java.lang.Integer" resultType="cn.zst.domain.User">
SELECT * from `user` WHERE id = #{id}
</select>
<select id ="findUserByName" parameterType="java.lang.String" resultType="cn.zst.domain.User">
SELECT * from `user` WHERE username like '%${value}%'
</select>
<delete id="delUserById" parameterType="java.lang.Integer" >
delete from `user` WHERE id=#{id}
</delete>
<update id="updateUserById" parameterType="java.lang.Integer" >
UPDATE `user` set username=#{username} WHERE id=#{id}
</update>
<insert id="insertUser" parameterType="cn.zst.domain.User" >
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT into `user` (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
</mapper>
接口文件(Mapper.java)
接口定义有如下规范:
- Mapper接口方法名和Mapper.xml中定义的statement的id相同
- Mapper接口方法的输入参数类型和Mapper.xml中定义的statement的parameterType的类型相同
- Mapper接口方法的输出参数类型和mapper.xml中定义的statement的resultType的类型相同
public interface UserMapper {
public User findUserById(Integer id);
//动态代理情况中,如果返回结果集为list,那么Mybatis会在生成实现类的时候,会自动调用selectList()方法
public List<User> findUserByName(String userName);
public void insertUser(User user);
public void delUserById(Integer id);
}
测试类
public class UserMapperTest {
private SqlSessionFactory factory;
//在测试方法前执行该方法,
@Before
public void setUp() throws Exception{
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() throws Exception{
SqlSession openSession = factory.openSession();
//通过getMapper方法来实例化接口
UserMapper mapper = openSession.getMapper(UserMapper.class);
User user = mapper.findUserById(1);
System.out.println(user);
}
}
小结
- selectOne()和selectList()
动态代理对象调用SqlSession.selectOne()和SqlSession.selectList()是根据mapper接口方法的返回值决定,如果返回list则调用selectList方法,如果返回单个对象就调用selectOne()方法 - namespace
Mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用POJO包装对象或map对象,保证dao的通用性。