动态代理实现dao接口
1、CRUD代码分析
- 规律: 参数结构相似, 参数1指定对应的statement, 参数2 实际需要参数值
2、思路分析
问题一、是否可以通过动态代理,代理UserDAO接口,然后动态生成内部的增删改查代码呢?
- 应该是可以的,因为代码有很多相似之处,都是调用SqlSession的增删改查方法,然后 调用 对应的 statement ,传递对应的Sql参数。
- 模式虽然类似,但是我们有新的问题:
问题二、想要让Mybatis帮我们动态生成DAO代码,有以下问题需要解决?
- A:如何确定要调用SqlSession的哪个方法?
- 每一个DAO中的方法,最终一定对应Mapper.xml中的一个statement,而每个statement中定义的Sql语句,就可以知道应该是增、删、改、查中的哪一个。
- 所以要确定应该调用SqlSession的哪个方法,就要先确定DAO的方法对应mapper.xml中的哪个statement
- B:如何确定要调用哪个statement?
- 要确定statement,就必须通过两个参数:mapper.xml文件的namespace + 这个statement的ID
- 而我们知道,这两个值都是用户自定义的,可以是任意值。如果是这样,我们是无法确定的!
- 但是,我们刚才定义namespace和ID并不是任意的:
namespace恰好就叫UserMapper,与UserDAO接口相关
statement的id恰好与每一个方法的名称一致。
其实呢,在Mybatis中,也是采用类似的约定来做的,mybatis对于Mapper.xml文件的定义有以下约定:
- 约定namespace必须与DAO接口的全名称一致
- 约定statement的ID必须和接口中的方法名称一致
- 这样以来,Mybatis的底层,只要拿到我们定义的接口,以及接口的方法名称,必然能确定到对应的statement,从而知道我们应该调用哪个SqlSession方法,知道返回值类型是什么,知道参数类型是什么,从而帮我们动态生成DAO的实现代码!
如何通知mybatis名称空间?
- 解决: 指定
名称空间
为dao接口的全路径
, 包名 + 类名
如何通知mybatis那条语句? sql语句的id
- 解决:
dao接口的方法名 和 mapper文件的id保持一致
3、mybatis动态代理实现dao接口
3.1、规则
- 要想动态代理实现dao接口的方法, 关键在于找到对应的statement. 如果想快速找到对应的statment, 就需要遵守如下约定:
每一个dao接口 都有一个对应的 XxxMapper.xml映射文件(必须)
-mapper.xml文件的namespace必须是接口的全名称(必须)
mapper.xml文件的每个statement的id必须是 dao接口的方法名(必须)
statement中定义的resultType必须和方法定义的返回值类型一致(必须)
- 在MyBatis中,
一般dao接口命名规则为 XxxMapper.java
, 不是 XxxxDao.java(虽然这不是必须的)
- 如果遵守以上约定, myBatis很对就可以实现动态代理
3.2、编写Mapper接口(代替dao接口)
.
package cn.hanjiaxiaozhi.mapper;
import cn.hanjiaxiaozhi.pojo.User;
import java.util.List;
public interface UserMapper {
/**
* 根据编号 查询用户
*/
public User queryUserById(Long id);
/**
* 查询所有用户
*/
public List<User> queryUserList();
/**
* 添加用户
*/
public void insertUser(User user);
/**
* 修改用户
*/
public void updateUser(User user);
/**
* 根据编号 删除客户
*/
public void deleteById(Long id);
}
3.3、修改 UserMapper.xml文件
xml文件的namespace必须是 mapper接口的全路径
每个statement的id 必须 mapper接口的方法名保持一致
<?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">
<!--
定义所有用到的sql语句 和 映射关系
namespace : 当前这个配置文件的唯一标识, 自定义, 但是不能和其他配置文件namespace重复
-->
<mapper namespace="cn.hanjiaxiaozhi.mapper.UserMapper">
<!--
定义一条sql语句, 其实就是一个 statement
select 代表是查询语句, 与其类似的还有 insert, update, delete等
id : 这条sql语句的唯一标识,自定义,但是不能和其他sql语句重复
parameterType: sql语句需要的参数类型, 需要写 类的全路径
resultType: sql语句返回的结果类型
-->
<select id="queryUserById" parameterType="java.lang.Long" resultType="cn.hanjiaxiaozhi.pojo.User">
<!--这里写具体的sql语句, #{}是占位符, 编译时会被替换成?, 然后注入真实参数-->
select *,user_name as userName from tb_user where id=#{id}
</select>
<!--查询所有用户-->
<select id="queryUserList" resultType="cn.hanjiaxiaozhi.pojo.User">
select *,user_name as userName from tb_user
</select>
<!--添加用户-->
<insert id="insertUser" parameterType="cn.hanjiaxiaozhi.pojo.User">
insert into tb_user
(
id,
user_name,
password,
name,
age,
sex,
birthday,
created,
updated
)
values (
null,
#{userName},
#{password},
#{name},
#{age},
#{sex},
#{birthday},
now(),
now()
)
</insert>
<!--修改用户-->
<update id="updateUser" parameterType="cn.hanjiaxiaozhi.pojo.User">
update tb_user
set
user_name = #{userName},
password = #{password},
name = #{name},
age = #{age},
sex = #{sex},
birthday = #{birthday},
updated = now()
where id = #{id}
</update>
<!--删除用户-->
<delete id="deleteById" parameterType="java.lang.Long">
delete from tb_user
where id = #{id}
</delete>
</mapper>
3.4 测试
.
package cn.hanjiaxiaozhi.mapper;
import cn.hanjiaxiaozhi.pojo.User;
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 org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
import static org.junit.Assert.*;
public class UserMapperTest {
private UserMapper userMapper;
@Before
public void setUp() throws Exception {
String resource = "mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
userMapper = sqlSession.getMapper(UserMapper.class);
}
@Test
public void queryUserById() throws Exception {
User user = userMapper.queryUserById(1L);
System.out.println(user);
}
@Test
public void queryUserList() throws Exception {
List<User> userList = userMapper.queryUserList();
for (User user : userList) {
System.out.println(user);
}
}
}
3.5、总结
- MyBatis动态代理生成dao的步骤:
- 编写数据管理的接口 XxxMapper.java
- 编写接口对应的配置文件 XxxxMapper.xml
- namespace必须 和 dao接口的全路径保持一致
- statement的id必须 和 dao接口的方法名保持一致
- statement的resultType类型 必须 和方法返回值类型保持一致
- 通过 sqlSession.getMapper(类的字节码对象) 获取代理之后的Mapper实现类对象