自定义mybatis框架

首先我们来回顾下MyBatis完成一个简单查询的步骤:
1.创建maven工程,导入相关依赖的jar包
2.创建实体类User
3.创建接口UserDao,定义方法findAll()
4.配置UserDao.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">
<mapper namespace="com.itheima.dao.UserDao">
    <select id="findAll" resultType="com.itheima.domain.User">
        select * from user
    </select>
</mapper>

5.配置核心配置文件SqlMapConfig.xml
 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///db1"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/itheima/dao/UserDao.xml"/>
    </mappers>
</configuration>

6.测试类
 

public class Test {
	public static void main(String[] args) throws IOException {
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		SqlSessionFactory factory = builder.build(Resources.getResourceAsReader("SqlMapConfig.xml"));
		SqlSession session = factory.openSession();
		UserDao userDao = session.getMapper(UserDao.class);
		List<User> list = userDao.findAll();
		for (User user : list) {
			System.out.println(user);
		}
		session.close();
	}
}

下面来自定义一个Mybatis框架完成这个最基本的功能(这里先写原理,源代码最后附上)
1.Resources:用于得到xml配置文件的输入流

public static InputStream getResourceAsStream(String filePath){
        return Resources.class.getClassLoader().getResourceAsStream(filePath);
    }

2.Mapper:存储sql语句和返回值类型(一个方法的mapper)

    private String queryString;//SQL
    private String resultType;//实体类的全限定类名

3.Configuration:存储sql连接的配置信息和mappers(所有方法的mapper,键值对中键为"全限定类名.方法名")

    private String driver;
    private String url;
    private String username;
    private String password;
    private Map<String,Mapper> mappers = new HashMap<String,Mapper>();

4.SqlSessionFactoryBuilder:
    利用配置文件的输入流创建sqlSessionFactory(XMLConfigBuilder工具类用于将配置文件信息加载至configuration对象),将configuration传递至sqlSessionFactory

    public SqlSessionFactory build(InputStream config){
        Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
        return  new DefaultSqlSessionFactory(cfg);
    }

5.SqlSessionFactory:创建sqlSession,将从sqlSessionFactoryFactoryBuilder传递过来的configuration传递至sqlSession

    private Configuration cfg;
    public DefaultSqlSessionFactory(Configuration cfg){
        this.cfg = cfg;
    }
    public SqlSession openSession() {
        return new DefaultSqlSession(cfg);
    }

6.SqlSession:通过传递过来的Configuration,得到一个数据库的连接connection(用到了DataSourceUtil)和所有mapper类mappers,创建        一个代理类并将解析出来的connection和mappers传递给代理类MapperProxy

    private Configuration cfg;
    private Connection connection;
    public DefaultSqlSession(Configuration cfg){
        this.cfg = cfg;
        connection = DataSourceUtil.getConnection(cfg);
    }
    public <T> T getMapper(Class<T> daoInterfaceClass) {
        return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
                new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
    }

7.MapperProxy:判断是否有执行的方法(如调用findAll方法便由它判断configuration中是否存储了相应的键值对),没有则抛出异常(这种情况就是配置文件未配置相应方法和它对应的sql语句),若有则将对应的mapper和conn交给Executor去执行sql操作

    private Map<String,Mapper> mappers;
    private Connection conn;
    public MapperProxy(Map<String,Mapper> mappers,Connection conn){
        this.mappers = mappers;
        this.conn = conn;
    }
    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);
    }

8.Executor拿到了存储sql语句和返回值类型的mapper对象和conn后,就可以执行sql语句了,使用java内省机制(借助PropertyDescriptor)实现对返回值对象属性的封装

    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);//要求:实体类的属性和数据库表的列名保持一种
                    //获取它的写入方法(setter方法)
                    Method writeMethod = pd.getWriteMethod();
                    //把获取的列的值,给对象赋值
                    writeMethod.invoke(obj,columnValue);
                }
                //把赋好值的对象加入到集合中
                list.add(obj);
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            release(pstm,rs);
        }
    }

至此,自定义框架mybatis封装完毕,运行test测试类可取出数据库数据
完整源代码下载链接: https://pan.baidu.com/s/1n7wX3nmtB1ecmIn_PRrlGw 密码: 9fc1

猜你喜欢

转载自blog.csdn.net/qq_38634814/article/details/82085960