深入Mybatis

1 前言

大家都知道MyBatis是我们常用的一个持久层框架,那么今天我们就来深入Mybatis底层,来探究MyBatis到底是怎么实现数据库操作的呢?

2 持久层的开发模式

1 传统的开发模式

接口定义业务方法

public interface UserService {
    public User getUserById(int id);
}

实现类实现接口方法 (sql语句写在java代码里面)

public class UserServiceImpl implements UserService{

    @Override
    public User getUserById(int id) {
        Connection conn = JDBCTools.getConnection();
        String sql = "select * from user where id = ?";
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, id);
            rs = pstmt.executeQuery();
            if(rs.next()){
                int sid = rs.getInt(1);
                String name = rs.getString(2);
                User user = new User(sid,name);
                return user;
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            JDBCTools.release(conn, pstmt, rs);
        }
        return null;
    }
}

测试

@Test
  public void f1() throws Exception{

      UserService userService = new UserServiceImpl();
      User user = userService.getUserById(1);
      System.out.println(user);

  }

2 Mybatis的开发模式

定义接口方法

public interface UserMapper {

    public User getUserById(int id);

}

接口对应的映射xml文件(sql语句写在xml文件)

<?xml version="1.0" encoding="UTF-8" ?>
 
 

    

测试

@Test
public void f1() throws Exception{

    UserMapper mapper = (UserMapper) new MyInvocationHandler().getInstance(UserMapper.class);
    User user = mapper.getUserById(1);
    System.out.println(user);

}

通过以上代码可以看到,MyBatis的方式省去了实现类的创建,改为用xml来定义业务方法的具体实现

为什么MyBatis可以不用通过实例化对象来操作我们的代码呢?这里就不得不说说jdk的动态代理

jdk动态代理运行时结合接口和mapper.xml来动态创建一个代理对象,程序调用该代理对象的方法来完成业务

3 jdk动态代理

创建一个类,实现InvocationHandler接口

1 自定义getInstance方法:入参为目标对象,通过Proxy.newProxyInstance方法创建代理对象,并返回

2 实现接口的invoke方法,通过反射机制完成业务逻辑代码

invoke方法是核心代码,在该方法中实现具体的业务需求

3 使用invoke方法解析数据库信息配置xml,创建数据库连接对象

//读取数据源配置信息
    public static Map getProperties(){
        Map map = new HashMap();
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read("src/config.xml");
            //获取根节点
            Element root = document.getRootElement();
            Iterator iter = root.elementIterator();
            while(iter.hasNext()){
                Element e = (Element) iter.next();
                //解析environments节点
                if("environments".equals(e.getName())){
                    Iterator iter2 = e.elementIterator();
                    while(iter2.hasNext()){
                        //解析environment节点
                        Element e2 = (Element) iter2.next();
                        Iterator iter3 = e2.elementIterator();
                        while(iter3.hasNext()){
                            Element e3 = (Element) iter3.next();
                            //解析dataSource节点
                            if("dataSource".equals(e3.getName())){
                                if("POOLED".equals(e3.attributeValue("type"))){
                                    Iterator iter4 = e3.elementIterator();
                                    //获取数据库连接信息
                                    while(iter4.hasNext()){
                                        Element e4 = (Element) iter4.next();
                                        map.put(e4.attributeValue("name"),e4.attributeValue("value"));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return map; 
    }
//获取信息,创建数据源对象
Map map = ParseXML.getProperties();
ComboPooledDataSource datasource = new ComboPooledDataSource();
datasource.setDriverClass(map.get("driver"));
datasource.setJdbcUrl(map.get("url"));
datasource.setUser(map.get("username"));
datasource.setPassword(map.get("password"));
datasource.setInitialPoolSize(20);
datasource.setMaxPoolSize(40);
datasource.setMinPoolSize(2);
datasource.setAcquireIncrement(5);
Connection conn = datasource.getConnection();

4 反射

数据库连接,接下来就需要获取待执行的SQL语句,sql的定义全部写在UserMapper.xml中;解析xml执行sql语句,执行完毕,查询结果会保存在ResultSet中,还需要将ResultSet对象中的数据进行解析,封装到JavaBean中返回

上述步骤通过两步实现

第一步:反射机制创建User对象

第二步:通过反射动态执行类中所有属性的setter方法,完成赋值

//获取sql语句
String sql = element.getText();
//获取参数类型
String parameterType = element.attributeValue("parameterType");
//创建pstmt
PreparedStatement pstmt = createPstmt(sql,parameterType,conn,args);
ResultSet rs = pstmt.executeQuery();
if(rs.next()){
    //读取返回数据类型
    String resultType = element.attributeValue("resultType");   
    //反射创建对象
    Class clazz = Class.forName(resultType);
    obj = clazz.newInstance();
    //获取ResultSet数据
    ResultSetMetaData rsmd = rs.getMetaData();
    //遍历实体类属性集合,依次将结果集中的值赋给属性
    Field[] fields = clazz.getDeclaredFields();
    for(int i = 0; i < fields.length; i++){
        Object value = setFieldValueByResultSet(fields[i],rsmd,rs);
        //通过属性名找到对应的setter方法
        String name = fields[i].getName();
        name = name.substring(0, 1).toUpperCase() + name.substring(1);
        String MethodName = "set"+name;
        Method methodObj = clazz.getMethod(MethodName,fields[i].getType());
        //调用setter方法完成赋值
        methodObj.invoke(obj, value);
        }
}

代码的实现大致思路如上所述,具体实现起来有很多细节需要处理

5 工具类

上述操作使用的两个工具类完整代码如下

ParseXML

public class ParseXML {

    //读取数据源配置信息
    public static Map getProperties(){
        Map map = new HashMap();
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read("src/config.xml");
            //获取根节点
            Element root = document.getRootElement();
            Iterator iter = root.elementIterator();
            while(iter.hasNext()){
                Element e = (Element) iter.next();
                //解析environments节点
                if("environments".equals(e.getName())){
                    Iterator iter2 = e.elementIterator();
                    while(iter2.hasNext()){
                        //解析environment节点
                        Element e2 = (Element) iter2.next();
                        Iterator iter3 = e2.elementIterator();
                        while(iter3.hasNext()){
                            Element e3 = (Element) iter3.next();
                            //解析dataSource节点
                            if("dataSource".equals(e3.getName())){
                                if("POOLED".equals(e3.attributeValue("type"))){
                                    Iterator iter4 = e3.elementIterator();
                                    //获取数据库连接信息
                                    while(iter4.hasNext()){
                                        Element e4 = (Element) iter4.next();
                                        map.put(e4.attributeValue("name"),e4.attributeValue("value"));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return map; 
    }

    //根据接口查找对应的mapper.xml
    public static String getMapperXML(String className){
        //保存xml路径
        String xml = "";
        SAXReader reader = new SAXReader();
        Document document;
        try {
            document = reader.read("src/config.xml");
            Element root = document.getRootElement();
            Iterator iter = root.elementIterator();
            while(iter.hasNext()){
                Element mappersElement = (Element) iter.next();
                if("mappers".equals(mappersElement.getName())){
                    Iterator iter2 = mappersElement.elementIterator();
                    while(iter2.hasNext()){
                        Element mapperElement = (Element) iter2.next();
                        //com.cehnjie.dao.StudentDAO . 替换 #
                        className = className.replace(".", "#");
                        //获取接口结尾名
                        String classNameEnd = className.split("#")[className.split("#").length-1];
                        String resourceName = mapperElement.attributeValue("resource");
                        //获取resource结尾名
                        String resourceName2 = resourceName.split("/")[resourceName.split("/").length-1];
                        //StudentDAO.xml . 替换 #
                        resourceName2 = resourceName2.replace(".", "#");
                        String resourceNameEnd = resourceName2.split("#")[0];
                        if(classNameEnd.equals(resourceNameEnd)){
                            xml="src/"+resourceName;
                        }
                    }
                }
            }
        } catch (DocumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return xml;
    }
}

MyInvocationHandler

public class MyInvocationHandler implements InvocationHandler{

    private String className;

    public Object getInstance(Class cls){
        //保存接口类型
        className = cls.getName();
        Object newProxyInstance = Proxy.newProxyInstance(  
                cls.getClassLoader(),  
                new Class[] { cls }, 
                this); 
        return (Object)newProxyInstance;
    }

    public Object invoke(Object proxy, Method method, Object[] args)  throws Throwable {        
        SAXReader reader = new SAXReader();
        //返回结果
        Object obj = null;
        try {
            //获取对应的mapper.xml
            String xml = ParseXML.getMapperXML(className);
            Document document = reader.read(xml);
            Element root = document.getRootElement();
            Iterator iter = root.elementIterator();
            while(iter.hasNext()){
                Element element = (Element) iter.next();
                String id = element.attributeValue("id");
                if(method.getName().equals(id)){
                    //获取信息,创建数据源对象
                    Map map = ParseXML.getProperties();
                    ComboPooledDataSource datasource = new ComboPooledDataSource();
                    datasource.setDriverClass(map.get("driver"));
                    datasource.setJdbcUrl(map.get("url"));
                    datasource.setUser(map.get("username"));
                    datasource.setPassword(map.get("password"));
                    datasource.setInitialPoolSize(20);
                    datasource.setMaxPoolSize(40);
                    datasource.setMinPoolSize(2);
                    datasource.setAcquireIncrement(5);
                    Connection conn = datasource.getConnection();
                    //获取sql语句
                    String sql = element.getText();
                    //获取参数类型
                    String parameterType = element.attributeValue("parameterType");
                    //创建pstmt
                    PreparedStatement pstmt = createPstmt(sql,parameterType,conn,args);
                    ResultSet rs = pstmt.executeQuery();
                    if(rs.next()){
                        //读取返回数据类型
                        String resultType = element.attributeValue("resultType");   
                        //反射创建对象
                        Class clazz = Class.forName(resultType);
                        obj = clazz.newInstance();
                        //获取ResultSet数据
                        ResultSetMetaData rsmd = rs.getMetaData();
                        //遍历实体类属性集合,依次将结果集中的值赋给属性
                        Field[] fields = clazz.getDeclaredFields();
                        for(int i = 0; i < fields.length; i++){
                            Object value = setFieldValueByResultSet(fields[i],rsmd,rs);
                            //通过属性名找到对应的setter方法
                            String name = fields[i].getName();
                            name = name.substring(0, 1).toUpperCase() + name.substring(1);
                            String MethodName = "set"+name;
                            Method methodObj = clazz.getMethod(MethodName,fields[i].getType());
                            //调用setter方法完成赋值
                            methodObj.invoke(obj, value);
                        }
                    }
                    conn.close();
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

       return obj;
    }

    /**
     * 根据条件创建pstmt
     * @param sql
     * @param parameterType
     * @param conn
     * @param args
     * @return
     * @throws Exception
     */
    public PreparedStatement createPstmt(String sql,String parameterType,Connection conn,Object[] args) throws Exception{
        PreparedStatement pstmt = null;
        try {
            switch(parameterType){
                case "int":
                    int start = sql.indexOf("#{");
                    int end = sql.indexOf("}");
                    //获取参数占位符 #{name}
                    String target = sql.substring(start, end+1);
                    //将参数占位符替换为?
                    sql = sql.replace(target, "?");
                    pstmt = conn.prepareStatement(sql);
                    int num = Integer.parseInt(args[0].toString());
                    pstmt.setInt(1, num);
                    break;
                case "java.lang.String":
                    int start2 = sql.indexOf("#{");
                    int end2 = sql.indexOf("}");
                    String target2 = sql.substring(start2, end2+1);
                    sql = sql.replace(target2, "?");
                    pstmt = conn.prepareStatement(sql);
                    String str = args[0].toString();
                    pstmt.setString(1, str);
                    break;
                default:
                    Class clazz = Class.forName(parameterType);
                    Object obj = args[0];
                    boolean flag = true;
                    //存储参数
                    List values = new ArrayList();
                    //保存带#的sql
                    String sql2 = "";
                    while(flag){
                        int start3 = sql.indexOf("#{");
                        //判断#{}是否替换完成
                        if(start3<0){
                            flag = false;
                            break;
                        }
                        int end3 = sql.indexOf("}");
                        String target3 = sql.substring(start3, end3+1);
                        //获取#{}的值 如#{name}拿到name
                        String name = sql.substring(start3+2, end3);
                        //通过反射获取对应的getter方法
                        name = name.substring(0, 1).toUpperCase() + name.substring(1);
                        String MethodName = "get"+name;
                        Method methodObj = clazz.getMethod(MethodName);
                        //调用getter方法完成赋值
                        Object value = methodObj.invoke(obj);
                        values.add(value);
                        sql = sql.replace(target3, "?");
                        sql2 = sql.replace("?", "#");
                    }
                    //截取sql2,替换参数
                    String[] sqls = sql2.split("#");
                    pstmt = conn.prepareStatement(sql);
                    for(int i = 0; i < sqls.length-1; i++){
                        Object value = values.get(i);
                        if("java.lang.String".equals(value.getClass().getName())){
                            pstmt.setString(i+1, (String)value);
                        }
                        if("java.lang.Integer".equals(value.getClass().getName())){
                            pstmt.setInt(i+1, (Integer)value);
                        }
                    }
                    break;
                }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return pstmt;
    }

    /**
     * 根据将结果集中的值赋给对应的属性
     * @param field
     * @param rsmd
     * @param rs
     * @return
     */
    public Object setFieldValueByResultSet(Field field,ResultSetMetaData rsmd,ResultSet rs){
        Object result = null;
        try {
            int count = rsmd.getColumnCount();
            for(int i=1;i<=count;i++){
                if(field.getName().equals(rsmd.getColumnName(i))){
                    String type = field.getType().getName();
                    switch (type) {
                        case "int":
                            result = rs.getInt(field.getName());
                            break;
                        case "java.lang.String":
                            result = rs.getString(field.getName());
                            break;
                    default:
                        break;
                    }
                }
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }


}

6 总结

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理 。多学习研究优秀框架的实现思路,对提升自己的编码能力大有裨益 。

页面更新:2024-05-22

标签:赋值   数据源   节点   反射   语句   属性   接口   对象   代码   方法

1 2 3 4 5

上滑加载更多 ↓
Top