Mybatis基础学习笔记(三)——自定义Mybtis
Mybatis基础学习笔记(三)
传统的dao执行sql
- 在前面直接使用Sqlsession执行sql语句时,与dao接口并没有直接关系
- 使用传统dao执行sql,方法是创建dao接口的实现类,然后配置mysql创建Sqlseesion对象执行sql语句,依然繁琐,重复
- 分析:如果使用传统方法实现sql语句调用,dao接口及其所调用的方法即为id坐标,方法的返回值即可选择Sqlsession对象需要执行的方法,这些信息都可以使用反射获取到
Mybatis框架执行sql语句
-
Mybatis根据配置信息获取执行sql所需要的信息,包括id坐标,返回值... ...使用动态代理技术,代理dao接口,实现dao接口的实现类的功能,免去了实现dao接口的繁琐
-
<!--观察mapper映射配置文件可以知道,方法的id坐标:namespace+id方法名,结果类型,方法参数... ...都包含--> <mapper namespace="com.itheima.dao.IAccountDao"> select="com.Xxx.dao.IUserDao.findById"></association> <!-- 查询所有 --> <select id="findAll" resultMap="accountUserMap"> select * from account </select>
-
使用Mybatis代理的要求:
- namespace必须是dao接口的全限定类名
- mapper文件中id方法名需要时接口中对应的方法名称
自定义Mybatis实现的关键步骤
-
自建SqlSession类,实现getMapper方法,根据传入的字节码,通过动态代理技术获取sql语句执行所需要的信息返回被代理的,接口的,代理对象proxy
-
public class DefaultSqlSession implements SqlSession { private Configuration cfg; private Connection connection; public DefaultSqlSession(Configuration cfg){ this.cfg = cfg; //根据配置对象获取连接信息,创建连接 connection = DataSourceUtil.getConnection(cfg); } /** * 用于创建代理对象 * @param daoInterfaceClass dao的接口字节码 */ @Override public <T> T getMapper(Class<T> daoInterfaceClass) { return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(), new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection)); } /** * 用于释放资源 */ @Override public void close() { if(connection != null) { try { connection.close(); } catch (Exception e) { e.printStackTrace(); } } } }
-
一个代理的执行具体增强对象
-
public class MapperProxy implements InvocationHandler { //map的key是全限定类名+方法名 private Map<String,Mapper> mappers; private Connection conn; public MapperProxy(Map<String,Mapper> mappers,Connection conn){ this.mappers = mappers; this.conn = conn; } /** * 用于对方法进行增强的,我们的增强其实就是调用selectList方法 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //1.获取方法名 String methodName = method.getName(); //2.获取方法所在类的名称 String className = method.getDeclaringClass().getName(); //3.组合key String key = className+"."+methodName; //4.获取mappers中的Mapper对象 Mapper mapper = mappers.get(key); //5.判断是否有mapper if(mapper == null){ throw new IllegalArgumentException("传入的参数有误"); } //6.调用工具类执行查询所有 return new Executor().selectList(mapper,conn); } }
-
一个执行具体操作的执行器
-
public class Executor { public <E> List<E> selectList(Mapper mapper, Connection conn) { PreparedStatement pstm = null; ResultSet rs = null; try { //1.取出mapper中的数据 String queryString = mapper.getQueryString();//select * from user String resultType = mapper.getResultType();//com.itheima.domain.User Class domainClass = Class.forName(resultType); //2.获取PreparedStatement对象 pstm = conn.prepareStatement(queryString); //3.执行SQL语句,获取结果集 rs = pstm.executeQuery(); //4.封装结果集 List<E> list = new ArrayList<E>();//定义返回值 while(rs.next()) { //实例化要封装的实体类对象 E obj = (E)domainClass.newInstance(); //取出结果集的元信息:ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); //取出总列数 int columnCount = rsmd.getColumnCount(); //遍历总列数 for (int i = 1; i <= columnCount; i++) { //获取每列的名称,列名的序号是从1开始的 String columnName = rsmd.getColumnName(i); //根据得到列名,获取每列的值 Object columnValue = rs.getObject(columnName); //给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装) PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种 //获取它的写入方法 Method writeMethod = pd.getWriteMethod(); //把获取的列的值,给对象赋值 writeMethod.invoke(obj,columnValue); } //把赋好值的对象加入到集合中 list.add(obj); } return list; } catch (Exception e) { throw new RuntimeException(e); } finally { release(pstm,rs); } } //释放资源 }