Mybatis学习笔记(三) 之Dao开发
使用Mybatis开发Dao,通常有两个方法,即原始Dao开发方法和Mapper接口开发方法,常用还是Mapper接口开发。
SqlSession的使用范围
public class test1 { private static SqlSessionFactory sqlSessionFactory; private static Reader reader; //创建会话工厂,传入mybatis的配置文件信息 static{ try{ //得到配置文件流 reader = Resources.getResourceAsReader("Configuration.xml"); //创建会话工厂,传入mybatis的配置文件信息 sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); }catch(Exception e){ e.printStackTrace(); } } //查询数据 public void select() { //通过工厂得到sqlsession SqlSession session = sqlSessionFactory.openSession(); try { //通过SqlSession操作数据库//第一个参数:映射文件中statement的id//第二个参数:指定和映射文件所匹配的parameterType类型参数 User user = (User) session.selectOne("com.yihaomen.mybatis.model.User.selectUserByID", 1); } finally { session.close(); } } }
想要通过mabatis对数据库操作,除了要各种配置之外,最终在测试的时候,需要创建特定对象,上图示例:
1、SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
2、SqlSessionFactory
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory
3、SqlSession
SqlSession是一个面向用户的接口, sqlSession中定义了数据库操作,默认使用DefaultSqlSession实现类。
SqlSession中封装了对数据库的操作,如:查询、插入、更新、删除等。通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。
结论:
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。
//通过工厂得到sqlsession SqlSession session = sqlSessionFactory.openSession(); try { } finally { session.close(); }
原始Dao开发方式
程序员需要编写DAO和DAO的实现类。需要向DAO实现类中注入SqlSessionFactory
,在方法体内通过SqlSessionFactory来创建SqlSession。
1、定义Dao接口
public interface UserDAO{ //根据本id查询用户 User findUserById(int id) throws Exception; //添加用户 void insertUser(User user) throws Exception; //根据id删除用户 void deleteUser(int id) throws Exception; }
2、Dao实现类
DAO实现类 /** * @ClassName: UserDAOImpl * @Description: DAO实现类(注意:SqlSession是非线程安全的,故不能声明为全局的) */ public class UserDAOImpl implements UserDAO { SqlSessionFactory sqlSessionFactory; /** * 向DAO实现类中注入SqlSessionFactory(此处通过构造方法注入) */ public UserDAOImpl(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; } @Override public User findUserById(int id) throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); User user = sqlSession.selectOne("test.findUserById", id); sqlSession.close(); return user; } @Override public void insertUser(User user) throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); sqlSession.insert("test.insertUser", user); sqlSession.commit(); sqlSession.close(); } @Override public void deleteUser(int id) throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); sqlSession.delete("test.deleteUser", id); sqlSession.commit(); sqlSession.close(); } }
3、新建一个源代码目录命名为test,在UserDAOImpl
类中鼠标右键新建一个Junit Test Case,更换源代码目录为test并勾选我们需要测试的方法:
public class UserDAOImplTest { private SqlSessionFactory sqlSessionFactory; /** * 此方法在执行测试之前执行,得到一个SqlSessionFactory */ @Before public void setUp() throws Exception { sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")); } @Test public void testFindUserById() throws Exception { // 创建UserDAO(在构造中注入SqlSessionFactory) UserDAO userDAO = new UserDAOImpl(sqlSessionFactory); User user = userDAO.findUserById(1); System.out.println(user); } @Test public void testInsertUser() throws Exception { UserDAO userDAO = new UserDAOImpl(sqlSessionFactory); User user = new User(); user.setSex("2"); user.setUsername("孙悟空"); user.setAddress("方寸灵台山"); user.setBirthday(new Date()); userDAO.insertUser(user); } @Test public void testDeleteUser() throws Exception { UserDAO userDAO = new UserDAOImpl(sqlSessionFactory); userDAO.deleteUser(27); } }
原始DAO开发中存在的问题
-
DAO的接口实现类中存在大量的模板方法,设想:可以将重复的代码提取出来。
-
在SqlSession的方法时将Statement的id硬编码在你DAO的实现类中。
-
调用sqlSession的相关方法传入参数是泛型,即使传入错误的参数,在编译阶段也不会报错,不利于程序员开发。
Mapper动态代理方式
实现原理
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper接口开发需要遵循以下规范:Mapper可以自动生成Mapper接口实现类代理对象。
1、 Mapper.xml文件中的namespace与mapper接口的类路径相同。
2、 Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
3、 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
4、 Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
1、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"> <!-- namespace命名空间,作用就是对sql进行分类化管理,注意:使用mapper代理方法开发,namespace有特殊重要作用 --> <mapper namespace="com.yihaomen.mybatis.dao.IUserOperation"> <!-- 在映射文件中配置很多sql --> <!-- id标识映射文件的sql,称为statement的id ,将sql语句封装到mappedStatement对象中,所以将id称为statement的id parameterType:指定输入类型 resultType:指定sql输出结果的所映射的java对象,select指定resultType表示将单挑记录 映射成java对象--> <select id="selectUserByID" parameterType="int" resultType="User"> select * from `user` where id = #{id} </select> <insert id="addUser" parameterType="User" useGeneratedKeys="true" keyProperty="id"> insert into user(userName,userAge,userAddress) values(#{userName},#{userAge},#{userAddress}) </insert> <update id="updateUser" parameterType="User"> update user set userName=#{userName},userAge=#{userAge},userAddress=#{userAddress} where id=#{id} </update> <delete id="deleteUser" parameterType="int"> delete from user where id=#{id} </delete> <select id="list" resultType="User"> select * from `user` </select> <!-- ${}表示拼接sql串,指定就是单挑记录所映射的java对象类型,使用${}拼接,容易导致sql注入 ${value}:拼接输入参数的内容,如果传入类型是简单类型,${}中只能使用value --> <select id="findUserByName" parameterType="String" resultType="User"> select * from `user` where username like '%${value}%' </select> </mapper>
2、UserMapper.java类
package com.yihaomen.mybatis.dao; import java.util.List; import com.yihaomen.mybatis.model.Article; import com.yihaomen.mybatis.model.User; //注意:接口名字必须与 xml中的namespace名字一样 2、接口实现方法每个名字 与xml中的id对应 public interface IUserOperation { //查询数据 public User selectUserByID(int id); //增加数据 public void addUser(User user); //更新数据 public void updateUser(User user); //删除数据 public void deleteUser(int id); //联合查询 public List<Article> getUserArticles(int id); //list获取 public List<User> list(); //模糊查询 public List<User> findUserByName(String name); }
完成前面2步之后不要忘了在映射文件SqlMapConfig.xml中加载UserMapper.xml哦!
<!-- 配置映射文件 --> <mappers> <mapper resource="sqlmap/User.xml"/> <mapper resource="mapper/UserMapper.xml"/> </mappers>
3、针对UserMapper接口测试
package com.yihaomen.mybatis.ui; import java.io.Reader; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import com.yihaomen.mybatis.dao.IUserOperation; import com.yihaomen.mybatis.model.User; public class Test { private static SqlSessionFactory sqlSessionFactory; private static Reader reader; //创建会话工厂,传入mybatis的配置文件信息 static{ try{ //得到配置文件流 reader = Resources.getResourceAsReader("Configuration.xml"); //创建会话工厂,传入mybatis的配置文件信息 sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); }catch(Exception e){ e.printStackTrace(); } } //公共方法,返回初始化的sqlSessionFactory对象 public static SqlSessionFactory getSession(){ return sqlSessionFactory; } //查询数据 public void select() { //通过工厂得到sqlsession SqlSession session = sqlSessionFactory.openSession(); try { //通过SqlSession操作数据库 //第一个参数:映射文件中statement的id //第二个参数:指定和映射文件所匹配的parameterType类型参数 User user = (User) session.selectOne("com.yihaomen.mybatis.model.User.selectUserByID", 1); System.out.println(user.getUserAddress()); System.out.println(user.getUserName()); } finally { session.close(); } } //增加数据 public void addUser(String address,String name){ //创建user对象 User user=new User(); user.setUserAddress(address); user.setUserName(name); user.setUserAge(80); //通过工厂得到SqlSession SqlSession session = sqlSessionFactory.openSession(); try { IUserOperation userOperation=session.getMapper(IUserOperation.class); //添加数据 userOperation.addUser(user); //提交 session.commit(); //System.out.println("当前增加的用户 id为:"+user.getId()); } finally { session.close(); } } //更新数据 public void updateUser(int id,String address){ //先得到用户,然后修改,提交。 SqlSession session = sqlSessionFactory.openSession(); try { IUserOperation userOperation = session.getMapper(IUserOperation.class); User user = userOperation.selectUserByID(id); user.setUserAddress(address); userOperation.updateUser(user); session.commit(); System.out.println("更新成功!!"); } finally { session.close(); } } //删除数据 public void deleteUser(int id) { SqlSession session = sqlSessionFactory.openSession(); try{ IUserOperation userOperation = session.getMapper(IUserOperation.class); userOperation.deleteUser(id); session.commit(); System.out.println("删除数据:id= "+id); }finally { session.close(); } } //list获取 public void getList() { SqlSession session = sqlSessionFactory.openSession(); try{ IUserOperation userOperation = session.getMapper(IUserOperation.class); List<User> us = userOperation.list(); session.commit(); //System.out.println("生成list: "+us.size()); }finally { session.close(); } } //模糊查询 public void geFindUserByName(String name) { SqlSession session = sqlSessionFactory.openSession(); try{ IUserOperation userOperation = session.getMapper(IUserOperation.class); List<User> us = userOperation.findUserByName(name); System.out.println(us.size()); session.commit(); }finally { session.close(); } } public static void main(String[] args) { Test test = new Test(); //test.getList(); test.geFindUserByName("小"); //test.addUser("杭州","小江"); } }
-
我们比较疑问的就是我们在UserMapper.xml的根据用户名查找用户的ResultType使用的是普通的POJO,但是我们自己的Mapper接口中返回值是List类型。这不就造成了类型不一致么?但是,实际上代理对象内部调用了
selectOne()
或者selectList()
,代理对象内部会自动进行判断是否是单独的POJO选用合适的方法。 -
Mapper接口中方法的参数只有一个是否会影响系统的维护?DAO层的代码是被业务层公用的,即使Mapper接口的参数只有一个我们也可以使用包装的POJO来满足系统需求。
-
注意:持久层中方法的参数中可以使用包装类型,但是Service层中不建议使用包装类型(不利于业务层的拓展维护)。