一路走到ORM框架都经历了什么(上)

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 一路走到ORM框架都经历了什么

从 JDBC 到 ORM(例:Mybatis)的演化过程

下面我将介绍Java操作Mysql数据的方式的演化过程,从最基本的JDBC到ORM框架的实现,每一次演化都是为了解决现有存在的问题。

JDBC

这里需要加入Mysql驱动包或者依赖.

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
</dependency>
ini


@Test
public void test1() throws SQLException {
    // 注册驱动
    Driver driver = new com.mysql.jdbc.Driver();
    // 设置配置
    Properties properties = new Properties();
    properties.setProperty("user","root");
    properties.setProperty("password","123456");
    String url = "jdbc:mysql://localhost:3306/orm";
    // 获取连接
    Connection connect = driver.connect(url, properties);
    // 执行SQL
    String sql = "select * from user";
    Statement statement = connect.createStatement();
    // 返回结果
    ResultSet resultSet = statement.executeQuery(sql);
    // 结束资源
    resultSet.close();
    statement.close();
    connect.close();
}

可以看到我们是通过Statement去执行的SQL语句,这里是存在SQL注入风险的,简单来说就是SQL是字符串拼接的,如果有恶意参数,会影响整个SQL的意思,所以后面我们使用PreparedStatement,也叫做预处理,执行SQL语句的参数用(?)来表示,使用set方法插入值。好处:1. 防止SQL注入 2. 减少编译次数,效率高 3. 不在使用SQL拼接,减少语法错误。

@Test
public void test1() throws SQLException {
    // 注册驱动
    Driver driver = new com.mysql.jdbc.Driver();
    // 设置配置
    Properties properties = new Properties();
    properties.setProperty("user","root");
    properties.setProperty("password","123456");
    String url = "jdbc:mysql://localhost:3306/orm";
    // 获取连接
    Connection connect = driver.connect(url, properties);
    // 执行SQL
    String sql = "select * from user where username =?";
    PreparedStatement statement = connect.prepareStatement(sql);
    statement.setString(1,"steak");
    // 返回结果
    ResultSet resultSet = statement.executeQuery();
    // 结束资源
    resultSet.close();
    statement.close();
    connect.close();
}

封装JDBC工具类

上面的操作,其中资源配置都是固定操作,所以我们可以直接写一个工具类来每次获取连接,进行操作,释放资源。

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils {
    private static String username;
    private static String password;
    private static String url;
    private static String driver;
    static {
        try {
            Properties properties = new Properties();
            // 将配置写入配置文件,更加灵活
            properties.load(new FileInputStream("src\mysql.properties"));
            username = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(url, username, password);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    public static void close(ResultSet set, Statement statement, Connection connection) throws SQLException {
        if (set != null){
            set.close();
        }
        if (statement != null) {
            set.close();
        }
        if (connection != null) {
            connection.close();
        }
    }
}

使用连接池

在上面我们每次操作数据库时,都是获取连接,操作,断开连接。

传统的JDBC操作会频繁的请求和验证,占用很多系统资源,导致服务崩溃,如果程序出现问题未能正常关闭,将导致数据库内存泄露,组织共导致重启数据库。

而且不能控制连接的数量,如果连接过多,可能导致Mysql崩溃。所以我们引入了连接池,让连接复用。这里我使用德鲁伊连接池,只用引入先关依赖即可:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

这里我直接把原先的获取连接方式,改为使用连接池获取。

import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils {
    private static DataSource dataSource;
    static {
        try {
            Properties properties = new Properties();
            // 将配置写入配置文件
            properties.load(new FileInputStream("src\mysql.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    public static void close(ResultSet set, Statement statement, Connection connection) throws SQLException {
        if (set != null){
            set.close();
        }
        if (statement != null) {
            set.close();
        }
        // 在数据库连接池技术中,close 不是真的断掉连接,而是把使用的 Connection 对象放回连接池
        if (connection != null) {
            connection.close();
        }
    }
}

Apache—DBUtils

上面的操作存在一个问题,关闭connection之后,resultSet结果集无法使用,且resultSet不利于数据的管理,所以每次我们查询了数据,可以手动的添加到List,Map等容器使用。还存在一个问题,每次查询返回的都是ResultSet,能不能通过泛型和反射,直接将数据封装成对象或者到List中。这些操作就被Apache—DBUtils完成了,是Apache提供的开源JDBC工具类库,且内部对SQL执行进行了线程安全保证。

<dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.6</version>
</dependency>
@Test
public void test1() throws SQLException {
    Connection connection = JDBCUtils.getConnection();
    QueryRunner queryRunner = new QueryRunner();
    String sql = "select * from user where id >= ?";
    List<User> list = queryRunner.query(connection, sql, new BeanListHandler<>(User.class));
    JDBCUtils.close(null, null, connection);
}

DAO与增删改查通用方法-BasicDao

Dao : data access object 数据访问对象

我们希望将增删改查一些公共的方法抽取出来,BasicDao是专门和数据库交互的,在BasicDao的基础上,实现一张表对应一个Dao,更好的完成功能,比如Customer表对应就是CustomerDao。

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class BasicDAO<T> {
    private QueryRunner qr = new QueryRunner();
    // 增删改操作
    public int update(String sql, Object... parameters) throws SQLException {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            int update = qr.update(connection, sql, parameters);
            return update;
        } catch (SQLException e) {
            throw new RuntimeException(e); //将编译异常->运行异常 ,抛出
        } finally {
            JDBCUtils.close(null, null, connection);
        }
    }
    // 查询并封装多个对象
    public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) throws SQLException {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            return qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtils.close(null, null, connection);
        }
    }
    // 查询并封装单个对象
    public T querySingle(String sql, Class<T> clazz, Object... parameters) throws SQLException {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
            //将编译异常->运行异常 ,抛出
        } finally {
            JDBCUtils.close(null, null, connection);
        }
    }
    // 查询一个值
    public Object queryScalar(String sql, Object... parameters) throws SQLException {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            return qr.query(connection, sql, new ScalarHandler(), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtils.close(null, null, connection);
        }
    }
}
class CustomerDao extends BasicDAO<CustomerDao> {
    /**
     * 封装自己的方法,并且由于是BasicDao的子类,可直接使用其定义的方法
     * 由于指定了泛型,也可以直接返回封装好的对象
     */
}

ORM

是什么?

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

也就是说我们能不能不写死SQL,将一个模型类与数据库中一张表做映射关系。我们只面向对象操作,比如save(new User)表示保存User对象到数据库中,一般我们会把User类与数据库中User表进行匹配与映射。这样我们也不用直接写SQL了。

ORM的优缺点

优点:简单,直接,面向对象操作

缺点:会牺牲程序的执行效率和会固定思维模式。 (用多了,SQL可能都不会写了,而且提供的方法是有限的,但你的需求是无限的,我深有体会,这里感谢鱼皮对我的帮助)。

常见ORM框架

  1. Mybatis 、Mybatis-plus(常用,直接看官方文档操作)
  2. Hibernate

上面就是Java对数据库操作的演化过程,各个阶段的代码只做了简单的演示,感兴趣可以再深入学习,我现在是常用Mybatis-plus,但由于傻瓜式操作,我更想了解一下底层,所以在想我们能不能自己写一个简单的ORM,不需要很完善,可以表达思想即可,这里我看了一篇文章写的很棒,下面的代码也是基于该作者的。


这里我们先声明一点,不管是什么操作,Mysql都是只认SQL,既然现在我们是面向对象操作,不去自己写SQL,那ORM底层就一定会通过某些操作,将我们的对象操作转写为SQL语句,最后执行。底层实际上还是基础的部分,只不过我们套了个壳子,让使用更方便了而已,这里需要的基本知识:注解,反射,JDBC基础

代码演示

我们既然要将实体类与数据库表做映射,那我们就应该在类上声明,它对应的哪个表。我们使用@Table 来声明

@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 加在类上,标识哪张表
public @interface Table {
    String value() default "";
}
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 加在属性上,标识主键
public @interface PrimaryKey {
    String value() default "";
}

对应的实体类

// lombok注解,简化代码
@Data
@AllArgsConstructor
@NoArgsConstructor
// 自定义注解,表示对应的user表
@Table("user")
public class User {
    @PrimaryKey
    private Integer id;
    private String username;
    private String gender;
    private String address;
}

底层还是需要获取连接,这里我们使用Druid连接池

/**
 * 注册驱动
 */
public class MyDataSource {
    protected DataSource dataSource;
    {
        com.alibaba.druid.pool.DruidDataSource dataSource = new com.alibaba.druid.pool.DruidDataSource();
        dataSource.setUrl("jdbc:mysql://1.14.74.242:3306/orm");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        this.dataSource = dataSource;
    }
}
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
数据采集 移动开发 前端开发
2023年, 前端路上的开源总结(最新更新)
2023年, 前端路上的开源总结(最新更新)
19 0
|
8月前
|
消息中间件 缓存 安全
讲理论,重实战!阿里独家SpringBoot王者晋级之路小册,太强了!
大家平时学习SpringBoot的方式也一般是看大量博客或者是找一些业界评价好点的书籍,虽然SpringBoot相关资料很多,但是大多不成体系,很少有真正有能从0到1,详解Spring Boot一切从代码案例出发的案头笔记。 今天给小伙伴分享的就是来自阿里的SpringBoot王者晋级之路小册,这份小册从SpringBoot的开发环境部署开始,把Spring Boot搭建Web项目、操作数据库、使用缓存、日志、整合安全框架、结合消息队列和搜索框架,以及在实际应用中的部署全部讲得清清楚楚。
|
3月前
|
前端开发 JavaScript API
拥抱华为,困难重重,第一天学习 arkUI,踩坑踩了一天
拥抱华为,困难重重,第一天学习 arkUI,踩坑踩了一天
|
7月前
|
SpringCloudAlibaba Java 开发者
现在国内最牛逼的 Spring CloudAlibaba全栈操作手册,不接受反驳
Spring Cloud Alibaba 近几年在受到国内不少开发者的广泛关注,也成为面试比较吃香的一个技能点了,如果你连Spring Cloud Alibaba 微服务生态都没用过,那么你可能就要被时代淘汰了。
73 0
|
10月前
|
SQL 关系型数据库 MySQL
一路走到ORM框架都经历了什么(下)
一路走到ORM框架都经历了什么
41 1
|
11月前
|
SQL XML 存储
MyBatis这样用,同事直呼哇塞,堪称最佳实践
MyBatis是一款非常流行的ORM框架,相信很多小伙伴都在使用。我们经常会把它和MyBatis-Plus或者MBG一起使用,用多了之后对于其一些常规操作就不太熟悉了。最近总结了下MyBatis的实用用法和技巧,希望对大家有所帮助!
|
11月前
|
NoSQL 算法 MongoDB
使用项目驱动学习编程的诀窍
使用项目驱动学习编程的诀窍
125 0
|
11月前
|
JSON 前端开发 Java
JavaWeb编年史(远古时期)
JavaWeb编年史(远古时期)
156 0
|
存储 Dubbo 小程序
Java项目是不是分布式,真有那么重要吗?
大概不知道从什么时候,「微服务」「分布式」这两个词又再次频繁出现在我的视线里。 「微服务」「分布式」在我刚毕业的时候还是比较关注的,那时候还入门了一把SpringCloud,写了一篇很长的文章,还是很顶的,有不少的大号都给我转载了,在知乎又获得了很多的赞。
141 0
|
SQL 存储 Java
【2021软件创新实验室暑假集训】SpringBoot框架
【2021软件创新实验室暑假集训】SpringBoot框架
【2021软件创新实验室暑假集训】SpringBoot框架