Mybatis的前世今生

JDBC

说到java操作数据库jdbc一定是饶不开的。Mybatis其实也就是对JDBC进行了封装

// 注册 JDBC 驱动 
Class.forName("com.mysql.cj.jdbc.Driver"); 

// 打开连接 
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring-shiwu? characterEncoding=utf-8&serverTimezone=UTC", "root", "123456"); 

// 执行查询 
stmt = conn.createStatement(); 
String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1"; ResultSet rs = stmt.executeQuery(sql); 

// 获取结果集 
while (rs.next()) { 
Integer id = rs.getInt("id"); 
String userName = rs.getString("user_name"); 
String realName = rs.getString("real_name"); 
String password = rs.getString("password"); 
Integer did = rs.getInt("d_id"); 
user.setId(id); 
user.setUserName(userName); 
user.setRealName(realName);
user.setPassword(password); 
user.setDId(did); 
System.out.println(user); 
}
  • jdbc操作步骤
    • Class.forName注册驱动
    • 获取一个Connection对象
    • 创建一个Statement对象
    • execute()方法执行SQL语句,获取ResultSet结果集
    • 通过ResultSet结果集给POJO的属性赋值
    • 最后关闭相关的资源

这种我们需要自己来维护管理资源的连接,如果忘记了,就很可能造成数据库服务连接耗尽。具体业务的SQL语句直接在代码中写死耦合性增强。此外,每个连接都会经历这几个步骤,重复代码很多。

  • 总结上面的操作的特点
    • 代码重复
    • 资源管理
    • 结果集处理
    • SQL耦合(即不灵活,每次都是重写sql语句,而不是一个相对来说万能一些的方法,可以动态的传入参数,然后动态的进行不同的功能)

JDBC优化1

先从代码重复和资源管理方面来优化,我们可以创建一个工具类来专门处理这个问题

public class DBUtils {

    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8&serverTimezone=UTC";
    private static final String JDBC_NAME = "root";
    private static final String JDBC_PASSWORD = "123456";

    private static  Connection conn;

    /**
     * 对外提供获取数据库连接的方法
     */
    public static Connection getConnection() throws Exception {
        if(conn == null){
            try{
                conn = DriverManager.getConnection(JDBC_URL,JDBC_NAME,JDBC_PASSWORD);
            }catch (Exception e){
                e.printStackTrace();
                throw new Exception();
            }
        }
        return conn;
    }

    /**
     * 关闭资源
     * @param conn
     */
    public static void close(Connection conn ){
        close(conn,null);
    }

    public static void close(Connection conn, Statement sts ){
        close(conn,sts,null);
    }

    public static void close(Connection conn, Statement sts , ResultSet rs){
        if(rs != null){
            try {
                rs.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        if(sts != null){
            try {
                sts.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        if(conn != null){
            try {
                conn.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

这样JDBC代码就可以调用这个工具类的相应的代码,看上去就会好一些了,但还不够

JDBC优化2

我们可以针对DML(即增删改)操作的方法来优化,先解决SQL耦合的问题,在DBUtils中封装DML操作的方法

/**
     * 执行数据库的DML操作
     * @return
     */
    public static Integer update(String sql,Object ... paramter) throws Exception{
        conn = getConnection();

         //动态的拼接参数
        PreparedStatement ps = conn.prepareStatement(sql);
        if(paramter != null && paramter.length > 0){
            for (int i = 0; i < paramter.length; i++) {
                //这个setObject方法是自带的api,就是封装参数的
                ps.setObject(i+1,paramter[i]);
            }
        }
        int i = ps.executeUpdate();
        //调用的工具类方法
        close(conn,ps);
        return i;
    }

这是JDBC代码简化如下

public void addUser(){
        String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values(?,?,?,?,?)";
        try {
            DBUtils.update(sql,"bobo","王五","111",22,1001);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

sql耦合的问题解决了,但还是没有解决ResultSet结果集的处理问题,所以我们还需要继续优化

JDBC优化3

要对结果集优化,即进行动态的赋值对象,不难想到要使用反射

public static <T> List<T> query(String sql, Class clazz, Object ... parameter) throws  Exception{
        //跟刚才的update方法基本一模一样
        conn = getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        if(parameter != null && parameter.length > 0){
            for (int i = 0; i < parameter.length; i++) {
                ps.setObject(i+1,parameter[i]);
            }
        }
        ResultSet rs = ps.executeQuery();

        // 获取对应的表结构的元数据
        ResultSetMetaData metaData = ps.getMetaData();
        List<T> list = new ArrayList<>();
        while(rs.next()){
            // 根据 字段名称获取对应的值 然后将数据要封装到对应的对象中
            int columnCount = metaData.getColumnCount();

            Object o = clazz.newInstance();
            //数据库中的列位移是从1开始的
            for (int i = 1; i < columnCount+1; i++) {
                // 根据每列的名称获取对应的值
                String columnName = metaData.getColumnName(i);

                //通过数据的列拿到结果集的对应字段的值
                Object columnValue = rs.getObject(columnName);

                //利用反射进行对象的赋值
                setFieldValueForColumn(o,columnName,columnValue);
            }
            list.add((T) o);
        }
        return list;
    }
private static void setFieldValueForColumn(Object o, String columnName,Object columnValue) {
        Class<?> clazz = o.getClass();
        try {
            // 根据字段获取属性
            Field field = clazz.getDeclaredField(columnName);
            // 私有属性放开权限
            field.setAccessible(true);
            field.set(o,columnValue);
            field.setAccessible(false);
        }catch (Exception e){
            // 说明不存在 那就将 _ 转换为 驼峰命名法  user_name  --> userName
            if(columnName.contains("_")){
                Pattern linePattern = Pattern.compile("_(\\w)");
                columnName = columnName.toLowerCase();
                Matcher matcher = linePattern.matcher(columnName);
                StringBuffer sb = new StringBuffer();
                while (matcher.find()) {
                    matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
                }
                matcher.appendTail(sb);
                // 再次调用复制操作
                setFieldValueForColumn(o,sb.toString(),columnValue);
            }
        }
    }

Apache DBUtils(效率很高)

DButils中提供了一个QueryRunner类,它对数据库的增删改查的方法进行了封装,获取QueryRunner的方式如下

druid.properties配置文件

druid.username=root
druid.password=admin
druid.url=jdbc:mysql://localhost:3306/spring-shiwu?characterEncoding=utf-8&serverTimezone=UTC
druid.minIdle=10
druid.maxActive=30
private static final String PROPERTY_PATH = "druid.properties";

    private static DruidDataSource dataSource;
    private static QueryRunner queryRunner;

    public static void init() {
        //这个api之前spring也用过
        Properties properties = new Properties();
        //加载配置文件
        InputStream in = DruidUtils.class.getClassLoader().getResourceAsStream(PROPERTY_PATH);
        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //建立数据源
        dataSource = new DruidDataSource();
        dataSource.configFromPropety(properties);
        // 使用数据源初始化 QueryRunner
        queryRunner = new QueryRunner(dataSource);
    }

QueryRunner中提供的方法解决了重复代码的问题,传入数据源解决了资源管理的问题。而对于ResultSet结果集的处理则是通过 ResultSetHandler 来处理(它是一个接口,提供了一些实现类)。我们也可以自己来实现该接口,或者用DBUtils中提供的默认的相关实现来解决,默认实现是ArrayHandler,handle方法把它转换成object数组

自己实现

public void queryUser() throws Exception{
        DruidUtils.init();
        QueryRunner queryRunner = DruidUtils.getQueryRunner();
        String sql = "select * from t_user";
        //ResultSet结果集的处理则是通过 ResultSetHandler 来处理。我们可以自己来实现该接口,这里是lambda表达式写法
        //或者用DBUtils中提供的默认的相关实现来解决,默认实现是ArrayHandler,handle把它转换成object数组
        List<User> list = queryRunner.query(sql, new ResultSetHandler<List<User>>() {
            @Override
            public List<User> handle(ResultSet rs) throws SQLException {
                List<User> list = new ArrayList<>();
                while(rs.next()){
                    User user = new User();
                    user.setId(rs.getInt("id"));
                    user.setUserName(rs.getString("user_name"));
                    user.setRealName(rs.getString("real_name"));
                    user.setPassword(rs.getString("password"));
                    list.add(user);
                }
                return list;
            }
        });
        for (User user : list) {
            System.out.println(user);
        }
    }

通过ResultHandle的实现类处理查询

public void queryUserUseBeanListHandle() throws Exception{
        DruidUtils.init();
        QueryRunner queryRunner = DruidUtils.getQueryRunner();
        String sql = "select * from t_user";
        // 不会自动帮助我们实现驼峰命名的转换
        List<User> list = queryRunner.query(sql, new BeanListHandler<User>(User.class));
        for (User user : list) {
            System.out.println(user);
        }
    }

Spring JDBC(效率很高)

在Spring中提供了一个模板方法JdbcTemplate,里面封装了各种各样的 execute,query和update方法。JdbcTemplate这个类是JDBC的核心包的中心类,简化了JDBC的操作,可以避免常见的异常,它封装了
JDBC的核心流程,应用只要提供SQL语句,提取结果集就可以了,它是线程安全的。

初始配置类

@Configuration
@ComponentScan
public class SpringConfig {

    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("admin");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring-shiwu?characterEncoding=utf-8&serverTimezone=UTC");
        return  dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        JdbcTemplate template = new JdbcTemplate();
        template.setDataSource(dataSource);
        return template;
    }
}

CRUD操作

@Repository
public class UserDao {

    @Autowired
    private JdbcTemplate template;

    public void addUser(){
        int count = template.update("insert into t_user(user_name,real_name)values(?,?)","bobo","波波老师");
        System.out.println("count = " + count);
    }



    public void query1(){
        String sql = "select * from t_user";
        List<User> list = template.query(sql, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setUserName(rs.getString("user_name"));
                user.setRealName(rs.getString("real_name"));
                return user;
            }
        });
        for (User user : list) {
            System.out.println(user);
        }
    }


    public void query2(){
        String sql = "select * from t_user";
        List<User> list = template.query(sql, new BeanPropertyRowMapper<>(User.class));
        for (User user : list) {
            System.out.println(user);
        }
    }

}

SpringJdbc的查询结果集映射跟Apache DBUtils同理的,代码基本完全一样

Hibernate

Apache DBUtils和SpringJdbcTemplate虽然简化了数据库的操作,但是本身提供的功能还是比较简单的(缺少缓存,事务管理等),所以我们在实际开发中往往并没有直接使用上述技术,而是用到了Hibernate(现在也很少用)和MyBatis等这些专业的ORM持久层框架。

什么是ORM

ORM( Object Relational Mapping) ,也就是对象与关系的映射,对象是程序里面的对象,关系是它
与数据库里面的数据的关系,也就是说,ORM框架帮助我们解决的问题是程序对象和关系型数据库的相
互映射的问题

Hebernate的使用

引入maven依赖

 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

实体类配置文件

<hibernate-mapping>
    <class name="com.gupaoedu.vip.model.User" table="t_user">
        <id name="id" />
        <property name="userName" column="user_name"></property>
        <property name="realName" column="real_name"></property>
    </class>
</hibernate-mapping>

Hibernate的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">
            com.mysql.cj.jdbc.Driver
        </property>
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&amp;serverTimezone=UTC
        </property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">123456</property>
        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLDialect
        </property>

        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.hbm2ddl.auto">update</property>

        <mapping resource="User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

CRUD 操作

public class HibernateTest {

    /**
     * Hibernate操作案例演示
     * @param args
     */
    public static void main(String[] args) {
        Configuration configuration = new Configuration();
        // 默认使用hibernate.cfg.xml
        configuration.configure();
        // 创建Session工厂
        SessionFactory factory = configuration.buildSessionFactory();
        // 创建Session
        Session session = factory.openSession();
        // 获取事务对象
        Transaction transaction = session.getTransaction();
        // 开启事务
        transaction.begin();
        // 把对象添加到数据库中
        User user = new User();
        user.setId(668);
        user.setUserName("hibernate-1");
        user.setRealName("持久层框架");
        session.save(user);
        transaction.commit();
        session.close();
    }
}

映射文件也可通过注解方式

Data
@Entity
@Table(name = "t_user")
public class User {

    @Id
    @Column(name = "id")
    private Integer id;

    @Column(name = "user_name")
    private String userName;

    @Column(name = "real_name")
    private String realName;

    @Column(name = "password")
    private String password;

    @Column(name = "age")
    private Integer age;

    @Column(name = "i_id")
    private Integer dId;
}

在Spring中给我们提供的JPA对持久层框架做了统一的封装,而且本质上就是基于HibernateJPA来实现
的,所以我们在使用的时候也可以通过SpringDataJPA的API来操作
dao的接口只需要继承JpaRepository接口(类似于mybatis-plus的那个继承)即可

public interface IUserDao extends JpaRepository<User,Integer> {
}
@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private IUserDao dao;

    @Override
    public List<User> query() {
        return dao.findAll();
    }

    @Override
    public User save(User user) {
        return dao.save(user);
    }
}

Hibernate优缺点

  • 优点
    • 根据数据库方言自定生成SQL,移植性好
    • 自动管理连接资源
    • 实现了对象和关系型数据的完全映射,操作对象就想操作数据库记录一样
    • 提供了缓存机制
  • 缺点
    • 比如API中的get(),update()和save()方法,操作的实际上是所有的字段,没有办法指定部分字段,
      换句话说就是不够灵活(因为它是全自动的)
    • 自定生成SQL的方式,如果要基于SQL去做一些优化的话,也是非常困难的。
    • 不支持动态SQL,比如分表中的表名,条件,参数变化等,无法根据条件自动生成SQL
      因此我们需要一个更为灵活的框架
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值