自定义持久层框架

1.前言

通过模仿MyBatis源码手写自定义持久层框架,命名规则,设计规范均参考MyBatis.

2.自定义框架设计思路

使用端:

提供配置文件
1.SqlMapConfig.xml

核心配置文件,存放数据源,引入mapper

2.Mapper.xml

自定义sql

3.创建实体类User
4.创建dao层接口
5.创建测试类进行测试

框架端:

1.读取配置文件(以流的形式保存在内存中)

1.创建Resources类:

静态方法:static InputStream getResourceAsStream(String path)
根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中

2.创建Configuration类:

对应SqlMapConfig.xml

3.创建MappedStatement类:

对应Mapper.xml

2.解析配置文件(从内存中读取保存在javabean中)

1.创建XMLConfigBuilder类:(封装Configuration)

方法:Configutation parseConfig(InputStream inputStream)

2.创建XMLMapperBuilder类:(封装MappedStatement,需要传入封装Configuration)

方法:void parse(InputStream inputStream)

3.创建 SqlSessionFactoryBuilder类:(Builder构建者设计模式)

方法:SqlSessionFactory bulid()

2.创建 SqlSessionFactory接口:(工厂模式)

方法:SqlSession openSession();

3.创建SqlSession接口(封装具体CRUD方法)

方法:selectList(String statementid,Object... params);
      selectOne(String statementid,Object... params);

3.自定义框架设计实现

3.1前提准备

1.创建两个Module

1)IPersistence
2)IPersistence_test
包名结构如图
image.png

2.pom.xml文件配置

1:IPersistence


    
    
        mysql
        mysql-connector-java
        5.1.17
    
     
    
        c3p0
        c3p0
        0.9.1.2
    
    
        log4j
        log4j
        1.2.12
    
    
        junit
        junit
        4.10
    
    
        dom4j
        dom4j
        1.6.1
    
    
        jaxen
        jaxen
        1.1.6
    

2:IPersistence_test
使用端只需要引入自定义框架



    
        com.lagou
        IPersistence
        1.0-SNAPSHOT
    

3.IPersistence_test pojo包下新增实体User

public class User {
    private Integer id;
    private String username;
    private String password;
    private String birthday;
    //省略getter setter 以及toString 方法
}

4.IPersistence_test dao包下新增接口IUserDao

public interface IUserDao {
    //查询所有
    public List findAll();
    //条件查询
    public User findUserByCondition(User user);
    //新增
    public void addUser(User user);
    //更新
    public void updateUser(User user);
    //删除
    public void deleteUserById(User user);
}

image.png

3.2配置文件准备

定义在IPersistence_test下的resources目录
所有节点名称均为自定义名称,没有约束头文件

1.核心配置文件:sqlMapConfig.xml


    
 
        
        
        
        
    
    
 

2.SQL语句文件:UserMapper.xml

namespace:该Mapper的唯一标识
id:sql的唯一标识
namespace+id:表示statementId(后面会用到)
resultType:返回值的全限定类名
paramterType:参数的全限定类名
#{}:表示占位符号


    
    
    

3.3读取配置文件

1.创建Javabean

MappedStatement
对应mapper.xml中的一个select标签
public class MappedStatement {
    //id标识
    private String id;
    //返回值类型
    private String resultType;
    //参数值类型
    private String paramterType;
    //标签中的sql语句
    private String sql;
    //省略getter setter 以及toString方法
}
Configuration
对应sqlMapConfig.xml
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

public class Configutation {
    //数据源
    private DataSource dataSource;
    Map mappedStatementMap = new HashMap<>();
    //省略getter setter 以及toString方法

}

因为sql标签存在多个,因此需要使用Map集合
Map key:statementid(上文提及) value:封装好的MappedStatement对象

2.创建Resources类

根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
public class Resources {

    public static InputStream getResourceAsStream(String path){
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
    
}

3.4解析配置文件

1.创建XMLConfigBuilder类

package com.lagou.config;
import com.lagou.io.Resources;
import com.lagou.pojo.Configutation;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
/**
 * @author 振帅
 * @create 2020/11/6 18:54
 * @description: XMLConfigBuilder 解析配置文件
 */
public class XMLConfigBuilder {
    private Configutation configutation;
    public XMLConfigBuilder(){
        this.configutation = new Configutation();
    }
    /**
    *该方法就是使用dom4j将配置文件进行解析 封装Configuration
    */ public Configutation parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
            Document document = new SAXReader().read(inputStream);
            //
            Element rootElement = document.getRootElement();
            List list = rootElement.selectNodes("//property");
            //用Map也可以
            Properties properties = new Properties();
                for (Element element : list) {
                    String name = element.attributeValue("name");
                    String value = element.attributeValue("value");
                    properties.setProperty(name,value);
                }
            //数据库连接池
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
            comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
            comboPooledDataSource.setUser(properties.getProperty("username"));
            comboPooledDataSource.setPassword(properties.getProperty("password"));
            configutation.setDataSource(comboPooledDataSource);
            //mapper.xml解析:拿到路径-字节输入流--dom4j解析
            List mapperList = rootElement.selectNodes("//mapper");
            for (Element element : mapperList) {
                String mapperPath = element.attributeValue("resource");
                InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configutation);
                xmlMapperBuilder.parse(resourceAsStream);
            }
            return configutation;
        }
}

主要是parseConfig方法,将InputStream转化为Configutation。

2.创建XMLMapperBuilder类

package com.lagou.config;
import com.lagou.pojo.Configutation;
import com.lagou.pojo.MappedStatement;
import com.lagou.pojo.MappedStatementEnum;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
/**
 * @author 振帅
 * @create 2020/11/6 19:35
 * @description: XMLMapperBuilder
 */
 public class XMLMapperBuilder {
    private Configutation configutation;
    public XMLMapperBuilder(Configutation configutation){
        this.configutation = configutation;
    }
    public void parse(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        //
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        List list = rootElement.selectNodes("//select");
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramterType = element.attributeValue("paramterType");
            String sqlText = element.getTextTrim();//去除空格
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setSql(sqlText);
            mappedStatement.setMappedStatementEnumEnum(MappedStatementEnum.SELECT);
            String key = namespace + "." + id;
            configutation.getMappedStatementMap().put(key,mappedStatement);
        }
       
    }
}

封装MappedStatement
构建statementId
完善Configutation 的 mappedStatementMap

3.创建SqlSessionFactoryBuilder类

package com.lagou.sqlSession;
import com.lagou.config.XMLConfigBuilder;
import com.lagou.pojo.Configutation;
import org.dom4j.DocumentException;
import java.beans.PropertyVetoException;
import java.io.InputStream;
/**
 * @author 振帅
 * @create 2020/11/6 18:49
 * @description: SqlSesstionFactoryBuilder*/
 public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) throws PropertyVetoException, DocumentException {
        // 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configutation configutation = xmlConfigBuilder.parseConfig(in);
        // 第二: 创建SqlSesstionFactory对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configutation);
        return defaultSqlSessionFactory;
    }
}

4.创建 SqlSessionFactory接口

package com.lagou.sqlSession;
/**
 * @author 振帅
 * @create 2020/11/6 18:53
 * @description: SqlSesstionFactory
 */
public interface SqlSessionFactory {
    public SqlSession openSession();
}

5.创建 SqlSessionFactory默认实现类

package com.lagou.sqlSession;
import com.lagou.pojo.Configutation;
/**
 * @author 振帅
 * @create 2020/11/6 20:09
 * @description: DefaultSqlSesstionFactory
 */
 public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private Configutation configutation;
    public DefaultSqlSessionFactory(Configutation configutation){
        this.configutation = configutation;
    }
    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configutation);
    }
}

6.创建 SqlSession接口

package com.lagou.sqlSession;
import java.util.List;
/**
 * @author 振帅
 * @create 2020/11/6 20:14
 * @description: SqlSesstion
 */
public interface SqlSession {
    //查询所有
    public  List selectList(String statementid,Object... params) throws Exception;
    //根据条件查询单个
    public  T selectOne(String statementid,Object... params) throws Exception;
    //新增
    public void update(String statementid,Object... params) throws Exception;
    //删除
    public void delete(String statementid,Object... params) throws Exception;
    //为dao接口生成代理实现类
    public  T getMapper(Class mapperClass);
}

7.创建 SqlSession默认实现类

package com.lagou.sqlSession;
import com.lagou.pojo.Configutation;
import com.lagou.pojo.MappedStatement;
import com.lagou.pojo.MappedStatementEnum;
import java.lang.reflect.*;
import java.util.List;
/**
 * @author 振帅
 * @create 2020/11/6 20:16
 * @description: DefaultSqlSession */public class DefaultSqlSession implements SqlSession {
    private Configutation configutation;
    public DefaultSqlSession(Configutation configutation){
        this.configutation = configutation;
    }
    @Override
 public  List selectList(String statementid, Object... params) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configutation.getMappedStatementMap().get(statementid);
        List list = simpleExecutor.query(configutation, mappedStatement, params);
        return (List) list;
    }
    @Override
 public  T selectOne(String statementid, Object... params) throws Exception {
        List objects = selectList(statementid, params);
        if (objects.size()==1) {
            return (T) objects.get(0);
        }else {
            throw new RuntimeException("查询结果过空或者返回结果过多");
        }
    }
    @Override
 public void update(String statementid, Object... params) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configutation.getMappedStatementMap().get(statementid);
        simpleExecutor.update(configutation,mappedStatement,params);
    }
    @Override
 public void delete(String statementid, Object... params) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configutation.getMappedStatementMap().get(statementid);
        simpleExecutor.delete(configutation,mappedStatement,params);
    }
    @Override
 public  T getMapper(Class mapperClass) {
        //使用jdk动态代理来为dao接口生成代理对象 并返回
 Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            //IUserDao userDao = sqlSession.getMapper(IUserDao.class);
 //userDao() 执行任何方法都要调用invoke
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
 // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
 String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId = className + "." + methodName;
                MappedStatement mappedStatement = configutation.getMappedStatementMap().get(statementId);
                Enum mappedStatementEnumEnum = mappedStatement.getMappedStatementEnumEnum();
                //返回类型
 Type genericReturnType = method.getGenericReturnType();
                if(mappedStatementEnumEnum == MappedStatementEnum.SELECT){
                    // 判断是否进行了 泛型类型参数化
 if(genericReturnType instanceof ParameterizedType){
                        List objects = selectList(statementId, args);
                        return objects;
                    }
                    return selectOne(statementId,args);
                }
                if(mappedStatementEnumEnum == MappedStatementEnum.INTERT){
                    update(statementId,args);
                    return null;
                }
                if(mappedStatementEnumEnum == MappedStatementEnum.UPDATE){
                    update(statementId,args);
                    return null;
                }
                if(mappedStatementEnumEnum == MappedStatementEnum.DELETE){
                    delete(statementId,args);
                    return null;
                }
                return null;
            }
        });
        return (T) proxyInstance;
    }
} 
 

3.2测试类编写

PS:我喜欢从使用入手,思路会比较清楚,报红先忍着

在IPersistence_test test包下新建IPersistenceTest.java
我们希望自定义框架能够拥有种MyBatis的类似的功能
1.定义接口IUserDao 不用写Dao的实现(代理模式)

public class IPersistenceTest{
    //读取到的字节流对象
    private InputStream resourceAsStream;
    private SqlSessionFactory sqlSesstionFactory;
    private SqlSession sqlSession;
    private IUserDao userDao;
    
    @Before
    public void before() throws Exception {
        resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //构建者设计模式获取sqlSesstionFactory 
        sqlSesstionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        sqlSession = sqlSesstionFactory.openSession();
        userDao = sqlSession.getMapper(IUserDao.class);
    }
    
    @Test
    public void test1() throws Exception {
        User user = new User();
        user.setId(1);
        user.setUsername("lucy");
        //普通模式
     List userList = sqlSession.selectList("com.lagou.dao.IUserDao.findUserByCondition",user);
        for (User user1 : userList) {
            System.out.println(user1);
        }
    }
    @Test
    public void test2() throws Exception {
        User user = new User();
        user.setId(1);
        user.setUsername("lucy");
        //代理模式
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        User userByCondition = userDao.findUserByCondition(user);
        System.out.println(userByCondition);
    }
}

@Before 注解可以定义在所有Test文件之前,在测试之前执行
通过Resources.getResourceAsStream("sqlMapConfig.xml")方法,将读取到的字节流对象保存在成员变量上。
涉及到的设计模式:
Builder构建者设计模式、工厂模式、代理模式