1. 程式人生 > 其它 >自定義Mybatis簡單實現方法

自定義Mybatis簡單實現方法

首先要明白mybatis為了解決jdbc存在的什麼問題,最簡單常見的就是以下幾個

  1. 資料庫連線建立、釋放頻繁造成系統資源浪費,影響系統性能

  2. SQL語句在程式碼中硬編碼,不移維護

  3. 預處理preparedStatement向佔位符傳參存在硬編碼

  4. 對結果集解析存在硬編碼

一.自定義框架設計思路

使用端:

提供配置檔案

SqlMapConfig.xml:存放資料來源資訊,引入mapper.xml

Mapper.xml:存放sql語句

自定義框架:

二、程式碼

Pom引入外掛

<dependencies>
        <dependency>
            <
groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</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> </dependencies>

Configuration和MapperdStatement

public class Configuration {

    private DataSource dataSource;

    // key: statementid  value:封裝好的mappedStatement物件
    Map<String,MappedStatement> mappedStatementMap = new HashMap<>();

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, MappedStatement> getMappedStatementMap() {
        return mappedStatementMap;
    }

    public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
        this.mappedStatementMap = mappedStatementMap;
    }
}
public class MappedStatement {

    //id標識
    private String id;
    //返回值型別
    private String resultType;
    //引數值型別
    private String parameterType;
    //sql語句
    private String sql;
    //執行方法是select還是update
    private String type;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getParameterType() {
        return parameterType;
    }

    public void setParameterType(String parameterType) {
        this.parameterType = parameterType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}

XmlConfigBulider和XmlMapperBulider

public class XmlConfigBuilder {

    private Configuration configuration;

    public XmlConfigBuilder() {
        this.configuration = new Configuration();
    }

    /**
     * 該方法就是使用dom4j對配置檔案進行解析,封裝到Configuration
     */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {

        Document document = new SAXReader().read(inputStream);
        //<configuration>
        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("//property");
        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"));

        configuration.setDataSource(comboPooledDataSource);

        //mapper.xml解析: 拿到路徑--位元組輸入流---dom4j進行解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");

        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
            XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsSteam);

        }
        return configuration;
    }
}
public class XmlMapperBuilder {

    private Configuration configuration;

    public XmlMapperBuilder(Configuration configuration) {
        this.configuration =configuration;
    }

    public void parse(InputStream inputStream) throws DocumentException {

        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();

        String namespace = rootElement.attributeValue("namespace");

        //解析select|insert|update|delete標籤
        List<Element> elementList = rootElement.selectNodes("//select|insert|update|delete");

        for (Element element : elementList) {
            String type= element.getName();
            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.setParameterType(paramterType);
            mappedStatement.setSql(sqlText);
            mappedStatement.setType(type);
            String key = namespace+"."+id;
            configuration.getMappedStatementMap().put(key,mappedStatement);
        }
    }
}

sqlSession

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementid, Object... params) throws Exception {

        //將要去完成對simpleExecutor裡的query方法的呼叫
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);

        return (List<E>) list;
    }

    @Override
    public <T> T selectOne(String statementid, Object... params) throws Exception {
        List<Object> objects = selectList(statementid, params);
        if(objects.size()==1){
            return (T) objects.get(0);
        }else {
            throw new RuntimeException("查詢結果為空或者返回結果過多");
        }
    }

    @Override
    public boolean update(String statementId, Object... params) throws Exception {
        //將要去完成對simpleExecutor裡的query方法的呼叫
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return simpleExecutor.update(configuration, mappedStatement, params);
    }

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK動態代理來為Dao介面生成代理物件,並返回
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @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;
                //獲取方法型別是select還是增刪改
                String type = configuration.getMappedStatementMap().get(statementId).getType();

                // 準備引數2:params:args
                if ("select".equals(type)){
                    // 獲取被呼叫方法的返回值型別
                    Type genericReturnType = method.getGenericReturnType();
                    // 判斷是否進行了 泛型型別引數化
                    if(genericReturnType instanceof ParameterizedType){
                        List<Object> objects = selectList(statementId, args);
                        return objects;
                    }
                    return selectOne(statementId,args);
                }else {
                    //返回值型別為void則表示是增刪改操作
                    return update(statementId,args);
                }
            }
        });
        return (T) proxyInstance;
    }
}
public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
        // 第一:使用dom4j解析配置檔案,將解析出來的內容封裝到Configuration中
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);
        
        // 第二:建立sqlSessionFactory物件:工廠類:生產sqlSession:會話物件
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        return defaultSqlSessionFactory;
    }
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    
    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}
public class SimpleExecutor implements  Executor {

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {

        // 獲取資料庫連線
        PreparedStatement preparedStatement = connectByJdbc(configuration, mappedStatement, params);

        // 執行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList<>();

        // 封裝返回結果集
        while (resultSet.next()){
            Object o =resultTypeClass.newInstance();
            //元資料
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                // 欄位名
                String columnName = metaData.getColumnName(i);
                // 欄位的值
                Object value = resultSet.getObject(columnName);
                //使用反射或者內省,根據資料庫表和實體的對應關係,完成封裝
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);
            }
            objects.add(o);
        }
        return (List<E>) objects;

    }

    @Override
    public boolean update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception{
        // 獲取資料庫連線
        PreparedStatement preparedStatement = connectByJdbc(configuration, mappedStatement, params);
        // 執行sql
        return preparedStatement.execute();
    }

    //使用jdbc進行資料庫連線
    private PreparedStatement connectByJdbc(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1. 註冊驅動,獲取連線
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 獲取sql語句 : 將#{}轉換成?佔位符 ,轉換的過程中,還需要對#{}裡面的值進行解析儲存
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3.獲取預處理物件:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. 設定引數
        //獲取到了引數的全路徑
        String parameterType = mappedStatement.getParameterType();
        Class<?> parameterTypeClass = getClassType(parameterType);

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            //反射
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            //暴力訪問
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);

            preparedStatement.setObject(i+1,o);
        }
        return  preparedStatement;
    }


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

    }

    /**
     * 完成對#{}的解析工作: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;
    }
}

解析類

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();
  }
}
public class ParameterMappingTokenHandler implements TokenHandler {
    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

    // context是引數名稱 #{id} #{username}

    @Override
    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;
    }

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

Test

@Test
    public void test() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //呼叫
        User user = new User();
        user.setId(1);
        user.setUsername("lucy");

        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        List<User> all = userDao.findAll();
        for (User user1 : all) {
            System.out.println(user1);
        }


    }

    @Test
    public void testAdd() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //呼叫
        User user = new User();
        user.setId(3);
        user.setUsername("zhangsan");
        user.setPassword("123");
        user.setBirthday("1999-02-02");

        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        userDao.addUser(user);
    }