mybatis01-自定义orm框架

一、jdbc访问数据库及其缺陷

1.1 jdbc访问数据库的代码

public static void main(String[] args) throws Exception {
        Connection connection =null;
        PreparedStatement preparedStatement =null;
        ResultSet resultSet = null;
        try{
            // Q1:数据库链接信息直接硬编码在代码理
            //Q2 :每次执行该方法都需要与数据库新建一个链接
            Class.forName("com.mysql.jdbc.Driver");
            connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8"
                    , "root", "root");
            //Q3 : 直接代码理写sql存在硬编码,这个经常会改动
            String sql = "select * from user where  id = ?";
            preparedStatement = connection.prepareStatement(sql);
            // Q4 :设置参数和封装返回值都很繁琐,且参数个数改动,代码都要跟着改,扩展性低
            preparedStatement.setObject(1,2);
            resultSet = preparedStatement.executeQuery();

            User user = new User();
            while (resultSet.next()){
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                user.setId(id);
                user.setUsername(username);
            }
            System.out.println(user);
        }catch (Exception e ){
            e.printStackTrace();
        } finally {
          //略
        }
    }

1.2 jdbc访问数据库存在得问题

  1. 数据库链接信息直接硬编码在代码里
  2. 每次执行该方法都需要与数据库新建一个连接,耗资源。
  3. 直接代码理写sql存在硬编码,这个经常会改动
  4. 设置参数和封装返回值都很繁琐,且参数个数改动,代码都要跟着改,扩展性低

二、自定义orm框架

2.1 针对jdbc问题的解决思路

针对jdbc存在的问题,可以做如下设计

  1. 用xml方式来管理数据库链接信息,sql文本、以及一些其他资源。
  2. 利用数据库连接池来避免每次获取新连接。
  3. 利用java反射来设置参数,和封装结果集

2.2 orm框架设计

2.2.1 使用端使用思路
  1. 调用orm jar包方法,加载
2.2.2 框架端设计思路
  1. 创建SqlMapConf.xml文件,该文件为全局文件,用来管理数据库连接等信息。
  2. 创建XxxMapper.xml文件,用来管理不同表得sql文本。
  3. 创建Resources类来加载xml文件到内存
  4. 利用dom4j来解析xml文件。
    4.1 创建XmlConfBuilder类来解析SqlMapConf.xml
    4.2 创建XmlMapperbuilder类来解析XxxMapper.xml
  5. 封装xml的解析结果
    5.1 用Configuration类来封装SqlMapConf.xml的解析结果。
    5.2 用MapperStatement类来封装XxxMapper.xml文件中的每个crud标签内容。
  6. 用SqlSessionFactory创建SqlSession (数据库会话对象)来实现不同的curd方法
  7. 创建Executor 用于具体编写jdbc代码,封装sql,设置参数,封装结果集等。

2.3 orm 具体实现

2.3.1使用端
SqlMapCOnfig.xml

该xml配置文件主要存放一些数据库连接信息,以及xxxMapper.xml文件路径

<Configuration>
    <datasource>
        <property name="driver" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbc-url" value="jdbc:mysql:///hupeng"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </datasource>
    <mapper resource="UserMapper.xml">
    </mapper>
</Configuration>
UserMapper.xml

该xml文件用于存放各种访问数据的sql。

  • 其中namespace.id也就是statementid需要全局唯一用于定位需要执行的sql。
  • paramterType用于封装参数
  • resultType表示封装成什么类型的结果集
<Mapper namespace="user">
	<!--user.findAll 表示statementId-->
    <select id = "findAll" resultType = "com.hp.pojo.User">
        select * from user
    </select>
    <select id = "findByCondition" resultType="com.hp.pojo.User" paramterType="com.hp.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>
</Mapper>
Test
    @Test
    public void test1() throws Exception {
        InputStream resources = Resources.getResources("SqlMapConf.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder(resources).build();
        Sqlsession sqlsession = sqlSessionFactory.openSession();
        List<User> users = sqlsession.selectList("com.hp.dao.UserDao.findAll");
        for (User user : users) {
            System.out.println(user);
        }
    }
2.3.2 自定义的orm端
pom.xml需要导入的依赖以及配置一些maven编译以及jdk版本信息
   <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
	<dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
         <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>
    </dependencies>
Configuration
  • 这里用单例获取全局配置文件,全局只需要一份即可。
  • Configuration里放了一个容器mapperstatementMap,用来装所有的sql。其中key值为全局唯一的statementId。有namespace.id组成。
@Data
public class Configuration {

   private static volatile Configuration configuration;

   private DataSource dataSource;
   private Map<String,MapperStatement> mapperStatementMap = new HashMap<>();

   private Configuration() {
   }

   public static Configuration getInstance(){
       if(configuration == null){
           synchronized (Configuration.class) {
               if(configuration == null){
                   configuration = new Configuration();
               }
           }
       }
       return configuration;
   }
}
MapperStatement

用于封装XxxMapper.xml文件里具体的执行sql。

@Data
public class MapperStatement {

    private String id ;
    private String sql;
    private String parameterType;
    private String resultType;
}
Resources

用于加载资源文件到内存

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

    private InputStream inputStream;
    private Configuration configuration;

    public SqlSessionFactoryBuilder(InputStream inputStream) {
        this.inputStream = inputStream;
        this.configuration = Configuration.getInstance();
    }
    /**
     * 构建SqlSessionFactory工chang
     */
    public SqlSessionFactory build() throws Exception{
        //解析 SqlConfXml
         XmlConfigerBuilder.parse(inputStream);
        return new SqlSessionFactory(configuration);
    }
}
XmlConfigerBuilder 解析SqlMapConf.xml
public class XmlConfigerBuilder {

    private static Configuration configuration;

    /**
     * 具体解析SqlMapXml的类
     * @param inputStream
     * @return
     */
    public static Configuration parse(InputStream inputStream) throws Exception {
        configuration = Configuration.getInstance();
        Document document =new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        List<Element> elements = rootElement.selectNodes("//property");
        Properties properties =new Properties();
        for (Element element : elements) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }

        ComboPooledDataSource c3P0PooledDataSource = new ComboPooledDataSource();
        c3P0PooledDataSource.setDriverClass(properties.getProperty("driver"));
        c3P0PooledDataSource.setJdbcUrl(properties.getProperty("jdbc-url"));
        c3P0PooledDataSource.setUser(properties.getProperty("username"));
        c3P0PooledDataSource.setPassword(properties.getProperty("password"));

        //解析UserMapper.xml
        List<Element> mapperElements = rootElement.selectNodes("//mapper");
        for (Element mapperElement : mapperElements) {
            String resource = mapperElement.attributeValue("resource");
            XmlMapperBuilder.parse(Resources.getResources(resource));
        }
        configuration.setDataSource(c3P0PooledDataSource);
        return configuration;
    }
 }
XmlMapperBuilder 解析XxxMapper.xml
public class XmlMapperBuilder {

    private static Configuration configuration;

    public static void parse(InputStream inputStream) throws Exception{
        configuration =Configuration.getInstance();
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        List<Element> selectElements = rootElement.selectNodes("//select");

        for (Element selectElement : selectElements) {
            String id = selectElement.attributeValue("id");
            String resultType = selectElement.attributeValue("resultType");
            String paramterType = selectElement.attributeValue("paramterType");
            String sql = selectElement.getTextTrim();
            MapperStatement mapperStatement = new MapperStatement();
            mapperStatement.setId(id);
            mapperStatement.setResultType(resultType);
            mapperStatement.setParameterType(paramterType);
            mapperStatement.setSql(sql);

            String statementId = namespace+"."+id;
            configuration.getMapperStatementMap().put(statementId,mapperStatement);
        }
    }
}
SqlSessionFactory
/**
 * 工厂模式, 生产sqlSession会话对象
 */
public class SqlSessionFactory {

    private static Configuration configuration;

    public SqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    public Sqlsession openSession(){
        return new DefultSqlSession(configuration);
    }
}
SqlSession接口 以及默认实现类DefaultSqlSession
public interface Sqlsession {

  <E> List<E> selectList(String statementId,Object... params) throws Exception;
  <T> T selectOne(String statementId,Object... params) throws Exception;

   <T> T getMapper(Class c);

}
public class DefultSqlSession  implements Sqlsession {

    private Configuration configurationn;

    public DefultSqlSession(Configuration configuration) {
        this.configurationn = configuration;
    }

    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        //根据statementId来获取MapperStatement对象
        MapperStatement mapperStatement = configurationn.getMapperStatementMap().get(statementId);
        //调用SqlExecutor(与jdbc打交道的执行器)
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        List<E> list = simpleExecutor.query(mapperStatement, configurationn, params);
        return list;
    }

    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<T> list = selectList(statementId, params);
        if(list ==null || list.size()==0){
            return null;
        }
        if(list.size()>1){
            throw new RuntimeException("返回的不止一条数据");
        }
        return list.get(0);
    }
}
Executor
public interface Executor {

    <E> List<E> query(MapperStatement mapperStatement, Configuration configuration, Object... param) throws Exception;
}
Executor实现类 SimplerExecutor
/**
 * 与jdbc交互
 */
public class SimpleExecutor implements Executor {
  
    public <E> List<E> query(MapperStatement mapperStatement, Configuration configuration, Object... param) throws Exception {

        Connection connection = configuration.getDataSource().getConnection();
        //sql里面的参数用?代替
        BoundSql boundSql =  getBoundSql(mapperStatement.getSql());
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        // 反射设置参数 
        Class<?> aClass = getClassType(mapperStatement.getParameterType());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        for (int i =0 ;i< parameterMappings.size();i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            //参数
            String content = parameterMapping.getContent();
            Field field = aClass.getDeclaredField(content);
            field.setAccessible(true);
            //通过传递具体对象即可得到具体对象该field的值
            Object value = field.get(param[0]);
            //设置值
            preparedStatement.setObject(i+1,value);
        }

        //封装结果集
        Class<?> resultTypeClass = getClassType(mapperStatement.getResultType());
        ResultSet resultSet = preparedStatement.executeQuery();
        List<E> resultList =new ArrayList<>();
        while (resultSet.next()){
            ResultSetMetaData metaData = resultSet.getMetaData();
            Object o = resultTypeClass.newInstance();
            for (int i=1;i<=metaData.getColumnCount();i++){
                //属性名和属性值
                String columnName = metaData.getColumnName(i);
                Object columnValue = resultSet.getObject(columnName);
                //创建属性描述器,为属性生成读写方法
                PropertyDescriptor propertyDescriptor =new PropertyDescriptor(columnName,resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,columnValue);
            }
            resultList.add((E)o);
        }
        return resultList;

    }

    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if(paramterType!=null){
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
        return null;

    }

    private BoundSql getBoundSql(String sql) {
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", tokenHandler);
        String sqlText = genericTokenParser.parse(sql);

        BoundSql boundSql =new BoundSql();
        boundSql.setSqlText(sqlText);
        boundSql.setParameterMappings(tokenHandler.getParameterMappings());
        return boundSql;
    }
}
BounderSql
  • 用来封装sql以及参数集,方便上面的exector类处理。
  • 其中用到了几个工具类,ParameterMappingTokenHandler、GenericTokenParser 主要用于替换sql文本中的#{}表达式
@Data
public class BoundSql {
    //用?做占位符的sql文本
    private String sqlText;
    private List<ParameterMapping> parameterMappings = new ArrayList<>();
}

三、自定义orm框架优化

通过上述我们的自定义框架,我们解决了JDBC操作数据库带来的一些问题:例如频繁创建释放数据库连接,硬编码,手动封装返回结果集等问题,但是现在我们继续来分析刚刚完成的自定义框架代码,有没有什么问题?问题如下

  1. dao的实现类中存在重复的代码,整个操作的过程模板重复
  2. dao的实现类中存在硬编码,调用sqlsession的方法时,参数statement的id硬编码。
    解决:用代理模式来创建接口的代理对象。

使用端测试类编写

    @Test
    public void test3() throws Exception{
        InputStream resources = Resources.getResources("SqlMapConf.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder(resources).build();
        Sqlsession sqlsession = sqlSessionFactory.openSession();
        User user =new User();
        user.setId(1);
        user.setUsername("zhangsan");
        UserDao userDao = sqlsession.getMapper(UserDao.class);
        List<User> all = userDao.findAll();
        for (User user1 : all) {
            System.out.println(user1);
        }
    }

使用jdk动态代理解决硬编码

在sqlSession中添加方法

   <T> T getMapper(Class c);

Default默认实现该方法

 @Override
    public <T> T getMapper(Class c) {
        //动态代理生成Dao层的实现类、
        Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{c}, new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //通过接口和类,找到statementId
                String methodName = method.getName();
                Class<?> declaringClass = method.getDeclaringClass();
                String name = declaringClass.getName();
                String statementId = name+"."+methodName;
                //如果方法的返回类型参数化了,说明是集合
                if(method.getGenericReturnType() instanceof ParameterizedType){
                    return selectList(statementId, args);
                }
                return  selectOne(statementId,args);
            }
        });

        return (T) proxy;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值