自定长久框架 mybatis

前言

JDBC操作数据库和Mybatis操作数据库,为什么应用Mybatis框架,而不去应用原生过的JDBC操作数据库呢?
带着这么几个问题,咱们先来看看原生的JDBC操作数据步骤!

JDBC操作数据库步骤

  • 1 加载驱动
  • 2 创立连贯
  • 3 编译sql语句
  • 4 设置参数
  • 5 执行sql语句
  • 6 获取后果返回集
  • 7 敞开连贯

JDBC操作数据库存在的几个痛点:

  • 1 首先第一步加载驱动,这个咱们齐全能够通过反射来解决,更换不同的数据库驱动;
  • 2 创立连贯,每一次操作数据库都要去现成的连贯数据库,如果操作数据库很频繁,这种开销很耗费资源,咱们能够采纳线程池,`的思路去解决!
  • 3 3,4步骤能够一起来看,编译sql语句,这个通常设置一些参数,如果是很多参数对象,常常改变比拟大,在硬编码过程中,略微操作不慎可能会代码出错,改变老本很高,耦合性很大!
  • 4 执行sql 没什么可说的;
  • 5 获取后果返回集,查问而言后果返回,每个查问承受的后果集不同,此处,对象不同,其余代码都是反复的;
  • 6 敞开连贯

以上几个剖析过程,简直每次操作都会面临数据库连贯,敞开,获取后果集(返回类型不通过,对象类型不同)反复代码很高,参数设置重大耦合,改变频繁,出错率高;

对此反复度高的代码,能够通过封装利用去解决;
对于驱动,能够通过配置文件,更换不同的数据库驱动;
对于频繁连贯,能够通过连接池去解决;
对于设置参数,获取后果集,能够通过配置文件,以及泛型,反射,去封装不同类型的返回后果集;

我的项目构造

### 须要用到的依赖

      <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>

自定义框架

客户端

##### 步骤

  • 首先定义数据库配置文件,配置引入的sqlMapper文件

    应用字节输出流将其加载到内存中,应用过dom4j解析封装成Configuration对象,重复使用;
  • 定义sqlMapper文件 用来编写sql语句,入参,出参类型

    加载解析封装对象MappedStatement用来保留每个sqlMapper每条sql语句的入参,出参类型以及sql操作类型;
定义数据库配置文件

首先咱们定义数据库配置文件sqlMapConfigration.xml

<configuration>    <!--数据库连贯信息-->    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>    <property name="jdbcUrl"  value="jdbc:mysql:///stu_test"></property>    <property name="username" value="root"></property>    <property name="password" value="root"></property>    <!--    配置mapper sql信息文件 会有多个-->    <mapper resource="mapper.xml"></mapper></configuration>

定义 Configration

用来保留 数据库配置信息 和 每个mapper中sql惟一类型 namespace.sql的id

public class Configration {    /**     * 数据源对象     */    private DataSource dataSource;    /**     * key 规定是namesapc + . + id(每个sql语句的id)  设置参数以及返回类型时候应用     */    private Map<String,MappedStatement> mapperStamentMap = new HashMap<>();    public DataSource getDataSource() {        return dataSource;    }    public void setDataSource(DataSource dataSource) {        this.dataSource = dataSource;    }    public Map<String, MappedStatement> getMapperStamentMap() {        return mapperStamentMap;    }    public void setMapperStamentMap(Map<String, MappedStatement> mapperStamentMap) {        this.mapperStamentMap = mapperStamentMap;    }}

定义xmlConfigerBuilder类解析

定义查问语句sql配置文件

mapper.xml

定义sql以及sql入参对象类型,sql查问返回类型

<mapper namespace="User"> <select id="selectOne" paramterType="com.udeam.com.udeam.pojo.User" resultType="com.udeam.com.udeam.pojo.User"> select * from user where id = #{id} and name =#{name} </select><select id="selectList" resultType="com.udeam.com.udeam.pojo.User"> select * from user </select> </mapper>
定义 MappedStatement 实体类

保留每个mapper中sql的sql语句类型 , sql入参,返回类型以及sql的id

public class MappedStatement {    /**     * sql xml语句id 示意每条sql的唯一性     */    private String id;    /**     * sql入参类型     */    private Class<?> paramType;    /**     * sql返回类型     */    private Class<?> resultType;    /**     * sql语句     */    private String sql;    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    public Class<?> getParamType() {        return paramType;    }    public void setParamType(Class<?> paramType) {        this.paramType = paramType;    }    public Class<?> getResultType() {        return resultType;    }    public void setResultType(Class<?> resultType) {        this.resultType = resultType;    }    public String getSql() {        return sql;    }    public void setSql(String sql) {        this.sql = sql;    }}

定义SqlSessionFactoryBuilderbuild()办法解析 sqlMapConfigration.xml

public class SqlSessionFactoryBuilder {    private Configration configration;    public SqlSessionFactoryBuilder() {        this.configration = new Configration();    }    public SqlSessionFactory build(InputStream inputStream) throws Exception {        //1 解析配置文件,封装Configuration        xmlConfigerBuilder xmlConfigerBuilder = new xmlConfigerBuilder(configration);        //2 解析        configration = xmlConfigerBuilder.parseConfigration(inputStream);        // 3 创立SqlSessionFactory        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configration);        return sqlSessionFactory;    }}

定义xmlConfigerBuilder类用于

public class xmlConfigerBuilder {    private Configration configration;    public xmlConfigerBuilder(Configration  configration) {        this.configration = configration;    }    /**     * 解析封装xml     * @return     */    public Configration parseConfigration(InputStream inputStream) throws Exception {        Document read = new SAXReader().read(inputStream);        //获取跟标签        Element rootElement = read.getRootElement();        //1 设置datasource属性        List<Element> elementList = rootElement.selectNodes("//property");        Properties properties = new Properties();        for (int i = 0; i < elementList.size(); i++) {            //设置属性值            String name = elementList.get(i).attributeValue("name");            String value = elementList.get(i).attributeValue("value");            properties.setProperty(name,value);        }        // 设置连接池属性 , 这里应用c3p0连接池        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();        comboPooledDataSource.setUser(properties.getProperty("username"));        comboPooledDataSource.setPassword(properties.getProperty("password"));        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));        //设置datasource        configration.setDataSource(comboPooledDataSource);        //2 封装解析mapper属性        // 读取mapper 设置mapper返回类型 以及sql等封装 MappedStatement 对象        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configration);        List<Element> list = rootElement.selectNodes("//mapper");        for (Element element : list) {            String resource = element.attributeValue("resource");            InputStream inputStream1 = Resource.inputStream(resource);            //读取每一个mapper xml            xmlMapperBuilder.parse(inputStream1);        }        return configration;    }}

定义XMLMapperBuilder类解析Mapper类中信息,解析Mapper封装配置类中每个sql的sql语句以及返回类型,入参类型

解析mapper 封装sql语句属性到MappedStatement

public class XMLMapperBuilder {    private Configration configration;    public XMLMapperBuilder(Configration configration) {        this.configration = configration;    }    public void parse(InputStream inputStream1) throws DocumentException, ClassNotFoundException {        Document read = new SAXReader().read(inputStream1);        Element rootElement = read.getRootElement();        //获取namespace        String namespace = rootElement.attributeValue("namespace");        //读取每一个查问标签        List<Element> list = rootElement.selectNodes("//select");        for (Element element : list) {            MappedStatement mappedStatement = new MappedStatement();            //获取sql            String sql = element.getTextTrim();            mappedStatement.setSql(sql);            //设置id            String id = element.attributeValue("id");            mappedStatement.setId(id);            //设置入参类型            String paramterType = element.attributeValue("paramterType");            mappedStatement.setParamType(getClassType(paramterType));            //设置返回类型            String resultType = element.attributeValue("resultType");            mappedStatement.setResultType(getClassType(resultType));            //设置mapperStamentMap            configration.getMapperStamentMap().put(namespace + "." + id, mappedStatement);        }    }    public Class<?> getClassType(String type) throws ClassNotFoundException {        if(type==null){            return null;        }        Class<?> clasz = Class.forName(type);        return clasz;    }}

定义Resource类读取xml

public class Resource {    /**     * 加载配置文件工具类     * @param name     * @return     * @throws FileNotFoundException     */    public static InputStream inputStream(String name) throws Exception {        //应用类加载器加载配置文件        InputStream inputStream = Resource.class.getClassLoader().getResourceAsStream(name);        return inputStream;    }}

查问

定义查问接口SqlSession
public interface SqlSession {    //查问多个    public <E>List<E> selectList(String statementId,Object...params) throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException;    //查问单个    public <T> T  selectOne(String statementId,Object...params) throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException;    public void close() throws Exception;}

sql语句执行器Excutor接口

public interface Excutor {    /**     * 查问接口     * @param configration 数据库配置类     * @param mappedStatement mapper 信息对象     * @param params 参数     * @param <E>     * @return     */    <E> List<E> query(Configration configration, MappedStatement mappedStatement,Object[] params) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException, IntrospectionException, InvocationTargetException;    void close() throws Exception;}

sql语句执行器实现类
具体的sql执行器 (mybatis 中有三个),默认的是SimpleExcutor

这里面对通过传入的配置文件以及具体的 key(namespace.sql id)从MappedStatement获取sql以及sql入参返回类型
而后通过反射区设置参数,获取后果返回集;

其中 BoundSql类是对xml中sql进行解决,将其转换为?占位符,解析出#{}外面的值进行存储,而后再去执行后续的赋值操作!

public class SimpleExcutor implements Excutor {    private Connection connection;    @Override    public <E> List<E> query(Configration configration, MappedStatement mappedStatement, Object[] params) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException, IntrospectionException, InvocationTargetException {        //1 获取连贯        connection = configration.getDataSource().getConnection();        //2 获取sql select * from user where id = #{id} and name = #{name}        String sql = mappedStatement.getSql();        //对sql进行解决    //转换sql语句: select * from user where id = ? and name = ? ,转换的过程中,还须要对#{}外面的值进行解析存储        BoundSql boundSql = getBoundSql(sql);        //最终的sql        String finalSql = boundSql.getSqlText();        // 3 预编译对象        PreparedStatement preparedStatement = connection.prepareStatement(finalSql);        //获取传入的参数类型        Class<?> paramType = mappedStatement.getParamType();        // 4 获取传入参数        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();        //设置参数        for (int i = 0; i < parameterMappingList.size(); i++) {            String content = parameterMappingList.get(i).getContent();            //反射设置值            Field declaredField = paramType.getDeclaredField(content);            //强制拜访            declaredField.setAccessible(true);            Object o = declaredField.get(params[0]);            //占位符设置值  列是从1开始的            preparedStatement.setObject(i + 1, o);            System.out.println(" 以后属性是 " + content + " 值是 : " + o);        }        // 5. 执行sql        ResultSet resultSet = preparedStatement.executeQuery();        //返回的参数类型        Class<?> resultType = mappedStatement.getResultType();        ArrayList<E> objects = new ArrayList<>();        while (resultSet.next()) {            //创建对象            Object o = (E) resultType.newInstance();            //获取数据库返回的列值 元数据            ResultSetMetaData metaData = resultSet.getMetaData();            //返回列总数            int columnCount = metaData.getColumnCount();            for (int i = 1; i <= columnCount; i++) {                //获取列值                String columnName = metaData.getColumnName(i);                //获取值                Object value = resultSet.getObject(columnName);                //应用内省技术 也能够应用反射技术                //创立属性形容器,为属性生成读写办法                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType);                //获取写办法                Method writeMethod = propertyDescriptor.getWriteMethod();                // 向类中写入值                writeMethod.invoke(o, value);            }            objects.add((E) o);        }        return objects;    }    @Override    public void close() throws Exception {        connection.close();    }    /**     * 实现对#{}的解析工作:1.将#{}应用?进行代替,2.解析出#{}外面的值进行存储     * @param sql     * @return     */    private BoundSql getBoundSql(String sql) {        //标记解决类:配置标记解析器来实现对占位符的解析解决工作        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);        //解析进去的sql        String parseSql = genericTokenParser.parse(sql);        //#{}外面解析进去的参数名称        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);        return boundSql;    }}

具体的实现查问接口如DefaultSqlSession
默认的sqlsession实现类(mybatis中默认的DefaultSqlSession)

public class DefaultSqlSession implements SqlSession {    private Configration configration;    //sql执行器    private Excutor simpleExcutor = new SimpleExcutor();    public DefaultSqlSession(Configration configration) {        this.configration = configration;    }    @Override    public <E> List<E> selectList(String statementId, Object... params) throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException {        //依据 statementId 获取 MappedStatement 对象        MappedStatement mappedStatement = configration.getMapperStamentMap().get(statementId);        //sql 执行器        List<Object> query = simpleExcutor.query(configration, mappedStatement, params);        return (List<E>) query;    }    @Override    public <T> T selectOne(String statementId, Object... params) throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException {        List<Object> objects = selectList(statementId, params);        if (objects==null || objects.size() == 0){            return null;        }        if (objects.size()>1){            throw new RuntimeException("存在多个值!");        }        return (T) objects.get(0);    }    @Override    public void close() throws Exception {        simpleExcutor.close();    }}

定义SqlSessionFactory工厂用来生产不同的sqlSession去执行sql
获取SqlSession 示例 以及对象接口

public interface SqlSessionFactory {    public SqlSession openSqlSession();}

具体工厂实现类,生产sqlsession对象执行增删改查操作

public class DefaultSqlSessionFactory  implements SqlSessionFactory {    private Configration configration;    public DefaultSqlSessionFactory(Configration configration) {        this.configration = configration;    }    @Override    public SqlSession openSqlSession() {        return new DefaultSqlSession(configration);    }

解析的#{id}成站位符?工具类,以及内省创建对象工具类
GenericTokenParser

public class GenericTokenParser {  private final String openToken; //开始标记  private final String closeToken; //完结标记  private final TokenHandler handler; //标记处理器  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {    this.openToken = openToken;    this.closeToken = closeToken;    this.handler = handler;  }  /**   * 解析${}和#{}   * @param text   * @return   * 该办法次要实现了配置文件、脚本等片段中占位符的解析、解决工作,并返回最终须要的数据。   * 其中,解析工作由该办法实现,解决工作是由处理器handler的handleToken()办法来实现   */  public String parse(String text) {    // 验证参数问题,如果是null,就返回空字符串。    if (text == null || text.isEmpty()) {      return "";    }    // 上面持续验证是否蕴含开始标签,如果不蕴含,默认不是占位符,间接原样返回即可,否则继续执行。    int start = text.indexOf(openToken, 0);    if (start == -1) {      return text;    }   // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终须要返回字符串的变量builder,    // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行上面代码    char[] src = text.toCharArray();    int offset = 0;    final StringBuilder builder = new StringBuilder();    StringBuilder expression = null;    while (start > -1) {     // 判断如果开始标记前如果有转义字符,就不作为openToken进行解决,否则持续解决      if (start > 0 && src[start - 1] == '\\') {        builder.append(src, offset, start - offset - 1).append(openToken);        offset = start + openToken.length();      } else {        //重置expression变量,防止空指针或者老数据烦扰。        if (expression == null) {          expression = new StringBuilder();        } else {          expression.setLength(0);        }        builder.append(src, offset, start - offset);        offset = start + openToken.length();        int end = text.indexOf(closeToken, offset);        while (end > -1) {////存在完结标记时          if (end > offset && src[end - 1] == '\\') {//如果完结标记后面有转义字符时            // this close token is escaped. remove the backslash and continue.            expression.append(src, offset, end - offset - 1).append(closeToken);            offset = end + closeToken.length();            end = text.indexOf(closeToken, offset);          } else {//不存在转义字符,即须要作为参数进行解决            expression.append(src, offset, end - offset);            offset = end + closeToken.length();            break;          }        }        if (end == -1) {          // close token was not found.          builder.append(src, start, src.length - start);          offset = src.length;        } else {          //首先依据参数的key(即expression)进行参数解决,返回?作为占位符          builder.append(handler.handleToken(expression.toString()));          offset = end + closeToken.length();        }      }      start = text.indexOf(openToken, offset);    }    if (offset < src.length) {      builder.append(src, offset, src.length - offset);    }    return builder.toString();  }}

ParameterMapping

public class ParameterMapping {    private String content;    public ParameterMapping(String content) {        this.content = content;    }    public String getContent() {        return content;    }    public void setContent(String content) {        this.content = content;    }}

ParameterMappingTokenHandler

public class ParameterMappingTokenHandler implements TokenHandler {    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();    // context是参数名称 #{id} #{username}    public String handleToken(String content) {        parameterMappings.add(buildParameterMapping(content));        return "?";    }    private ParameterMapping buildParameterMapping(String content) {        ParameterMapping parameterMapping = new ParameterMapping(content);        return parameterMapping;    }    public List<ParameterMapping> getParameterMappings() {        return parameterMappings;    }    public void setParameterMappings(List<ParameterMapping> parameterMappings) {        this.parameterMappings = parameterMappings;    }}

TokenHandler

public interface TokenHandler {  String handleToken(String content);}
一般测试
    String name = "sqlMapConfigration.xml";    // 1 加载xml配置文件    InputStream inputStream = Resource.inputStream(name);    // 2 解析配置文件       //初始化Configration 初始化容器 mapperStamentMap容器保留mapper中sql 信息    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();    // 3 创立会话    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);    SqlSession sqlSession = sqlSessionFactory.openSqlSession();    //4 查问    User user = new User();    user.setId(2);    user.setName("小王");        //非代理模式    User user1 = sqlSession.selectOne("User.selectOne", user);    System.out.println(user1);    List<User> usersList = sqlSession.selectList("User.selectList");    System.out.println(usersList); 

这是应用namespace.id形式硬编码去查问,但理论过程中咱们间接通过service层调用dao层mapper去查问执行的;

故此,咱们须要想mybatis那样定义一个mapper接口类,而后应用动静代理调用执行;

在SqlSession接口中定义一个mapper代理接口

    /**     * Mapper代理接口     * @param mapperClass     * @param <T>     * @return     */    public <T> T getMapper(Class<?> mapperClass);
UserMapper创立
public interface UserMapper {    /**     * 查问所有     * @return     */    List<User> selectList2();    /**     * 查问单个 依据条件     * @param user     * @return     */    User selectOne2(User user);}

新创建mapper2.xml

<!--mapper代理模式 语句id 必须和mapper 中查问语句办法名保持一致 namespace 必须是类的权限定命名 起因是JDK动静代理中 无奈提供对应的namespace和查问语句配置id 故此用办法名和mapper类的全限定命名进行应用 key从获取mapper配置文件sql语句的入参,返回类型;--><mapper namespace="com.udeam.test.mapper.UserMapper">    <!--    示意查问单个-->    <select id="selectOne2" paramterType="com.udeam.pojo.User" resultType="com.udeam.pojo.User">        select * from user where id = #{id} and name = #{name}    </select>    <!--    示意查问多个-->    <select id="selectList2" resultType="com.udeam.pojo.User">        select * from user    </select></mapper>

而后在SqlMapXml里增加进去mapper

 <mapper resource="mapper2.xml"></mapper>

在子类中去实现这个代理办法

这里须要留神的是

  • 在上面jdk代理外面,咱们无奈拿到xml文件里namespace和sql id
  • 实际上应用mybatis时候,mapper代理的办法名,和mapper,xml里的会保持一致,namspacs会应用该mapper的权限定名;

在JDK动静代理中应用办法名和全门路去从Configration封装的map对象去获取xml里的sql的入参类型和返回类型;

    /**     * 应用JDK动静代理来执行mapper     */    @Override    public <T> T getMapper(Class<?> mapperClass) {        Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {            @Override            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                //获取class的权限定命名                String className = method.getDeclaringClass().getName();                //获取办法名                String name = method.getName();                //拼接statementid 从map中获取sql 入参类型,返回类型                String statementid = className + "." + name;                //判断是否实现泛型类型参数化                Type genericReturnType = method.getGenericReturnType();                if (genericReturnType instanceof ParameterizedType) {                    //还是去执行查询方法                    return selectList(statementid, args);                }                return selectOne(statementid, args);            }        });       return (T) o;    }
mapper代理测试
  // 代理测试        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);        System.out.println(userMapper.selectList2());

以上仅仅是实现了单表的查问操作,和传入的固定参数,对动静sql和删除在底部源代码中实现了,能够下载下来康康,删除和新增根本实现形式一样;

用到的设计模式

  • 工厂模式
    在创立不同的sqlSession时进行应用,能够抉择new 不同的子类;
  • 代理模式
    应用JDK动静代理对Mapper进行代理
  • 构建者模式
    在SqlSessionFactoryBuilder类中build()办法中通过对Configration 对象的属性构建;

代码地址

点击下载