天天看点

MyBatis之应用分析(为啥用Mybatis)

文章目录

  • ​​1.为什么要用MyBatis​​
  • ​​1.1.原始的数据库连接方式:使用 JDBC 的 API​​
  • ​​1.1.1.操作步骤​​
  • ​​1.注册jdbc驱动​​
  • ​​2.获取连接​​
  • ​​3.创建Statement对象,Statement对象定义了好多方法​​
  • ​​4.执行SQL​​
  • ​​5.处理结果集​​
  • ​​1.1.2.弊端​​
  • ​​1.2.java程序中简单的操作数据库的方式​​
  • ​​1.2.1.Apache DbUtils​​
  • ​​12.1.1.官网​​
  • ​​1.2.1.2.解决的问题​​
  • ​​1.2.1.3.原理​​
  • ​​A.QueryRunner​​
  • ​​1.2.2.Spring JDBC​​
  • ​​1.2.3.Apache DBUtil 和Spring JDBC的有点​​
  • ​​1.2.3.Apache DBUtil 和Spring JDBC的缺点​​
  • ​​1.3.ORM框架​​
  • ​​1.3.1.概念​​
  • ​​1.3.2.hibernate​​
  • ​​1.3.3.mybatis​​
  • ​​1.3.3.1.优点​​
  • ​​1.3.4.ORM 框架的选择​​

1.为什么要用MyBatis

1.1.原始的数据库连接方式:使用 JDBC 的 API

1.1.1.操作步骤

1.注册jdbc驱动

.forName("com.mysql.jdbc.Driver");      

2.获取连接

= DriverManager.getConnection(url, name, password);      

3.创建Statement对象,Statement对象定义了好多方法

= conn.createStatement();      
MyBatis之应用分析(为啥用Mybatis)

4.执行SQL

= "SELECT id, name, pwd FROM emp";
  ResultSet rs = stmt.executeQuery(sql);      

5.处理结果集

while(rs.next()){
    int bid = rs.getInt("bid");
    String name = rs.getString("name");
    String prd= rs.getString("pwd");
  }  
}      

1.1.2.弊端

1.需要手动开启和关闭数据库连接资源,有可能会忘记关闭资源,造成数据库连接耗尽;
2.业务代码和处理数据的代码耦合在一起,如果数据库交互次数多,SQL语句会非常多,耦合度太高;
  一旦修改业务代码,同时需要修改数据库等相关操作,工作量太大;
3.结果集需要手动处理;      

1.2.java程序中简单的操作数据库的方式

1.2.1.Apache DbUtils

12.1.1.官网

​​https://commons.apache.org/proper/commons-dbutils/​​

1.2.1.2.解决的问题

1.Apache DbUtils 主要解决的问题是结果集ResultSet映射成JavaBean的问题;      

1.2.1.3.原理

A.QueryRunner
1.Apache DbUtils 提供了一个QueryRunner,封装了对数据库的增删改查的操作;      
MyBatis之应用分析(为啥用Mybatis)
2.QueryRunner提供一个构造方法    public QueryRunner(DataSource ds),需要传入一个数据源DataSource,这样我们就不用再去自己写开启和关闭数据库连接的方法;      
private <T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params)
            throws SQLException {
        if (conn == null) {
            throw new SQLException("Null connection");
        }

        if (sql == null) {
            if (closeConn) {
                close(conn);
            }
            throw new SQLException("Null SQL statement");
        }

        if (rsh == null) {
            if (closeConn) {
                close(conn);
            }
            throw new SQLException("Null ResultSetHandler");
        }

        PreparedStatement stmt = null;
        ResultSet rs = null;
        T result = null;

        try {
            stmt = this.prepareStatement(conn, sql);
            this.fillStatement(stmt, params);
            rs = this.wrap(stmt.executeQuery());
            result = rsh.handle(rs);

        } catch (SQLException e) {
            this.rethrow(e, sql, params);

        } finally {
            try {
                close(rs);
            } finally {
                close(stmt);
                if (closeConn) {
                    close(conn);
                }
            }
        }

        return result;
    }      
3.那我们怎么把结果集转换成对象呢?比如实体类 Bean 或者 List 或者 Map?
  在Apache DbUtils 里面提供了一系列的支持泛型的 ResultSetHandler。      
MyBatis之应用分析(为啥用Mybatis)
MyBatis之应用分析(为啥用Mybatis)

实际上用的是反射的原理

MyBatis之应用分析(为啥用Mybatis)

1.2.2.Spring JDBC

除了 Apache DbUtils 之外,Spring 也对原生的 JDBC 进行了封装,
并且给我们提供了一个模板方法 JdbcTemplate,来简化我们对数据库的操作。      
第一个,我们不再需要去关心源管理的问题。      
MyBatis之应用分析(为啥用Mybatis)
第二个,对于结果集的处理,Spring JDBC 也提供了一个 RowMapper 接口,
可以把结果集转换成 Java 对象。      

RowMapper 源码

package org.springframework.jdbc.core;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.lang.Nullable;

@FunctionalInterface
public interface RowMapper<T> {
    @Nullable
    T mapRow(ResultSet var1, int var2) throws SQLException;
}      

例如

我们要把结果集转换成 Employee 对象,就可以针对一个 Employee创建一个RowMapper 对象,
实现 RowMapper 接口,并且重写 mapRow()方法。 
我们在 mapRow()方法里面完成对结果集的处理      
import com.gaoxinfu.crud.bean.Employee;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

public class EmployeeRowMapper implements RowMapper {
    @Override
    public Object mapRow(ResultSet resultSet, int i) throws SQLException {
        Employee employee = new Employee();
        employee.setEmpId(resultSet.getInt("emp_id"));
        employee.setEmpName(resultSet.getString("emp_name"));
        employee.setGender(resultSet.getString("gender"));
        employee.setEmail(resultSet.getString("email"));

        return employee;
    }
}      

在 DAO 层调用的时候就可以传入自定义的 RowMapper 类,

最终返回我们需要的类型。

结果集和实体类类型的映射也是自动完成的;

public List<Employee> query(String sql){
        JdbcTemplate jdbcTemplate= new JdbcTemplate( new DruidDataSource());
        return jdbcTemplate.query(sql,new EmployeeRowMapper());
    }      
MyBatis之应用分析(为啥用Mybatis)
但是还是有问题:每一个实体类对象,都需要定义一个 Mapper,然后要编写每个
字段映射的 getString()、 getInt 这样的代码,还增加了类的数量。
所以有没有办法让一行数据的字段,跟实体类的属性自动对应起来,实现自动映射呢?
当然,我们肯定要解决两个问题,
第一个就是名称对应的问题,从下划线到驼峰命名
第二个是类型对应的问题,数据库的 JDBC 类型和 Java 对象的类型要匹配起来。

我们可以创建一个 BaseRowMapper<T>,通过反射的方式自动获取所有属性,把表字段全部赋值到属性。
上面的方法就可以改成:
return jdbcTemplate.query(sql,new BaseRowMapper(Employee.class));      
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.jdbc.core.RowMapper;

public class BaseRowMapper<T> implements RowMapper<T> {

    private Class<?> targetClazz;
    private HashMap<String, Field> fieldMap;

    public BaseRowMapper(Class<?> targetClazz) {
        this.targetClazz = targetClazz;
        fieldMap = new HashMap<>();
        Field[] fields = targetClazz.getDeclaredFields();
        for (Field field : fields) {
            fieldMap.put(field.getName(), field);
        }
    }

    @Override
    public T mapRow(ResultSet rs, int arg1) throws SQLException {
        T obj = null;

        try {
            obj = (T) targetClazz.newInstance();

            final ResultSetMetaData metaData = rs.getMetaData();
            int columnLength = metaData.getColumnCount();
            String columnName = null;

            for (int i = 1; i <= columnLength; i++) {
                columnName = metaData.getColumnName(i);
                Class fieldClazz = fieldMap.get(camel(columnName)).getType();
                Field field = fieldMap.get(camel(columnName));
                field.setAccessible(true);

                // fieldClazz == Character.class || fieldClazz == char.class
                if (fieldClazz == int.class || fieldClazz == Integer.class) { // int
                    field.set(obj, rs.getInt(columnName));
                } else if (fieldClazz == boolean.class || fieldClazz == Boolean.class) { // boolean
                    field.set(obj, rs.getBoolean(columnName));
                } else if (fieldClazz == String.class) { // string
                    field.set(obj, rs.getString(columnName));
                } else if (fieldClazz == float.class) { // float
                    field.set(obj, rs.getFloat(columnName));
                } else if (fieldClazz == double.class || fieldClazz == Double.class) { // double
                    field.set(obj, rs.getDouble(columnName));
                } else if (fieldClazz == BigDecimal.class) { // bigdecimal
                    field.set(obj, rs.getBigDecimal(columnName));
                } else if (fieldClazz == short.class || fieldClazz == Short.class) { // short
                    field.set(obj, rs.getShort(columnName));
                } else if (fieldClazz == Date.class) { // date
                    field.set(obj, rs.getDate(columnName));
                } else if (fieldClazz == Timestamp.class) { // timestamp
                    field.set(obj, rs.getTimestamp(columnName));
                } else if (fieldClazz == Long.class || fieldClazz == long.class) { // long
                    field.set(obj, rs.getLong(columnName));
                }

                field.setAccessible(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return obj;
    }

    /**
     * 下划线转驼峰
     * @param str
     * @return
     */
    public static String camel(String str) {
        Pattern pattern = Pattern.compile("_(\\w)");
        Matcher matcher = pattern.matcher(str);
        StringBuffer sb = new StringBuffer(str);
        if(matcher.find()) {
            sb = new StringBuffer();
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
            matcher.appendTail(sb);
        }else {
            return sb.toString();
        }
        return camel(sb.toString());
    }
}      

1.2.3.Apache DBUtil 和Spring JDBC的有点

1.方法封装:CRUD
2.支持数据源的打开和关闭
3.映射结果集;      

1.2.3.Apache DBUtil 和Spring JDBC的缺点

DBUtil 和Spring Jdbc 实际上都是对 JDBC 做了轻量级封装的框架

但是还是存在一些缺点:
1、 SQL 语句都是写死在代码里面的,依旧存在硬编码的问题;      
MyBatis之应用分析(为啥用Mybatis)
2、 参数只能按固定位置的顺序传入(数组),它是通过占位符去替换的,不能自动映射;
3、 在方法里面,可以把结果集映射成实体类,但是不能直接把实体类映射成数据库的记录(没有自动生成 SQL 的功能);
4、 查询没有缓存的功能。      

1.3.ORM框架

1.3.1.概念

ORM 的全拼是 Object Relational Mapping,也就是对象与关系的映射,
对象是程序里面的Class对象属性,关系是它与数据库里面的表数据字段的关系。 
也就是说,ORM 框架帮助我们解决的问题是程序对象和关系型数据库的相互映射的问题。      

1.3.2.hibernate

1.3.3.mybatis

1.3.3.1.优点

1.使用连接池对连接进行管理,主要是连接的开启和关闭
2.SQL 和代码分离,集中管理
3.结果集映射:class对象与表字段的映射
4.参数映射和动态 SQL
5.重复 SQL 的提取:提取SQL的标签,然后引用即可
6.缓存管理
7.插件机制:插件可以使用      

1.3.4.ORM 框架的选择

1、业务简单的项目可以使用Hibernate
2、需要灵活的SQL,可以用MyBatis,可以自动生成,也可以自己写SQL
3、对性能要求高,可以使用原生的JDBC API
4、Spring JDBC可以和ORM框架混用