ORM框架介绍

ORM全称Object-Relational Mapping,中文翻译对象关系映射,主要实现程序对象到关系数据库数据的映射。

01 什么是ORM

对象关系映射(Object-Relational Mapping),对象为应用程序实例对象,关系为对象与数据库表数据关系。ORM框架作为数据库链接桥梁,通过持久化类与数据库表映射关系,解决面向对象与关系数据库互不匹配现象技术。

02 为什么需要ORM

如果不使用ORM框架开发应用程序,可能存在编写大量数据访问层冗余代码,用于操作数据库保存、删除和读取对象信息。相反,使用ORM则会大大减少重复性代码。

03 ORM演化历程

ORM经历纯JDBC、优化JDBC、Apache DBUtils、Spring JdbcTemplate、Hibernate、Mybatis、Spring Data JPA和MyBatis Plus几个阶段发展历程,ORM框架最终演化企业级应用框架Hibernate和Mybatis。

04 JDBC

4.1 原生JDBC

Java提供DriverManager使用SPI机制加载驱动类进行创建链接对象,数据库链接创建Statement进行SQL操作,获取到结果集ResultSet处理数据。需要注意,无用的链接需要及时关闭避免操作资源浪费。

原始JDBC操作存在一些缺陷,首先,需要大量冗余代码维护链接资源和结果集处理,代码臃肿难以维护。其次,存在很多SQL语句写在业务代码,导致代码耦合性太强,难以维护。

java 复制代码
public class SqlMain {
    public static void main(String[] args) throws Exception {
        String jdbcUrl = "";
        String jdbcUser = "";
        String jdbcPwd = "";
        String sql = "select * from user";

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

        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPwd);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)
        ){
            // 获取结果集
            while (rs.next()) {
                Integer id = rs.getInt("id");
                String user = rs.getString("user_name");
                String address = rs.getString("address");
                System.out.printf("{id=%d, user=%s, address=%s}\n", id, user, address);
            }
        }
    }
}

4.2 JDBC优化v1.0

根据JBC冗余特点,封装工具类优化重复代码和数据库链接资源管理。

java 复制代码
public class DBUtils {
    private static final String JDBC_URL = "";
    private static final String JDBC_NAME = "";
    private static final String JDBC_PASSWORD = "";
    
    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) {
                throw new Exception(e);
            }
        }
        return 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) {
        close0(conn);
        close0(sts);
        close0(rs);
    }

    private static void close0(AutoCloseable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

4.3 JDBC优化v2.0

封装DML操作方法,解决SQL耦合问题。

java 复制代码
public class DBUtils {
    // 数据库DML操作
    public static Integer update(String sql, Object... params) throws Exception {
        conn = getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);
            }
        }
        int count = ps.executeUpdate();
        close(conn, ps);
        return count;
    }
}

4.4 JDBC优化v3.0

最后引入反射和元数据技术,优化结果集处理代码冗余,以及业务耦合问题。

java 复制代码
public class DBUtils {
    // 查询方法封装
    public static <T> List<T> query(String sql, Class<?> clazz, Object... parameter) throws Exception {
        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> dataList = new ArrayList<>();

        // 根据字段名称获取数据值, 封装数据到对象
        while (rs.next()) {
            Object o = clazz.newInstance();

            int columnCount = metaData.getColumnCount();
            for (int i = 1; i < columnCount + 1; i++) {
                // 根据每列的名称获取对应的值
                String columnName = metaData.getColumnName(i);
                Object columnValue = rs.getObject(columnName);
                setFieldValueForColumn(o, columnName, columnValue);
            }
            dataList.add((T) o);
        }
        return dataList;
    }

    // 根据字段名称设置对象属性值
    private static void setFieldValueForColumn(Object o, String columnName, Object columnValue) {
        Class<?> clazz = o.getClass();

        String fileName = columnName;

        // 驼峰处理
        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);
            fileName = sb.toString();
        }

        try {
            // 根据字段获取属性
            Field field = clazz.getDeclaredField(fileName);
            // 私有属性放开权限
            field.setAccessible(true);
            field.set(o, columnValue);
            field.setAccessible(false);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

05 Apache DBUtils

Apache DbUtils库旨在简化JDBC使用,通过抽取JDBC资源清理,减少资源没有及时关闭导致链接泄露难以追踪问题。另外,提供自动实现ResultSet填充Java Bean属性,提供简洁干净数据结果处理逻辑。

Apache DBUtils官网链接地址:commons.apache.org/proper/comm...

5.1 初始化配置

Apache DButils提供QueryRunner,封装数据库增删改查获取QueryRunner。自定义封装数据库连接池管理(DataSource)初始化QueryRunner,初始化配置文件为druid.properties

groovy 复制代码
implementation group: 'commons-dbutils', name: 'commons-dbutils', version: '1.8.1'
implementation group: 'com.alibaba', name: 'druid', version: '1.1.14'
properties 复制代码
druid.username=
druid.password=
druid.url=
druid.minIdle=10
druid.maxActive=30
java 复制代码
public class DruidUtils {
    private static final String PROPERTY_PATH = "druid.properties";

    private static DruidDataSource dataSource;
    private static QueryRunner queryRunner;

    public static void init() {
        Properties properties = new Properties();
        InputStream in = DruidUtils.class.getClassLoader().getResourceAsStream(PROPERTY_PATH);
        try {
            properties.load(in);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        dataSource = new DruidDataSource();
        dataSource.configFromPropety(properties);
        
        // 使用数据源初始化QueryRunner
        queryRunner = new QueryRunner(dataSource);
    }

    public static QueryRunner getQueryRunner() {
        check();
        return queryRunner;
    }

    public static Connection getConnection() {
        check();
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public static void close(Connection connection) {
        try {
            if (Objects.nonNull(connection) && !connection.isClosed()) {
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private static void check() {
        if (Objects.nonNull(dataSource) || Objects.nonNull(queryRunner)) {
            throw new RuntimeException("DataSource has not been init");
        }
    }
}

5.2 CURD操作

QueryRunner提供解决重复代码问题,ResultSet结果集通过ResultSetHandler处理,额外传入数据源是为了解决资源管理的问题。

java 复制代码
public class TestCURD {
    // 添加用户
    @Test
    public void addUser() throws SQLException {
        DruidUtils.init();
        QueryRunner queryRunner = DruidUtils.getQueryRunner();
        String sql = "insert into t_user(user_name,real_name)values('aaa','bbbb')";
        int i = queryRunner.update(DruidUtils.getConnection(), sql);
        System.out.println(i);
    }

    // 查询所有用户信息
    @Test
    public void queryUser() throws Exception {
        DruidUtils.init();
        QueryRunner queryRunner = DruidUtils.getQueryRunner();
        String sql = "select * from t_user";
        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实现类处理查询
    @Test
    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);
        }
    }
}

06 Spring JDBC

Spring提供JdbcTemplate封装和简化JDBC操作,内部包含很多堕胎executequeryupdate操作数据库SQL处理方法。

6.1 初始化配置

Spring JdbcTemplate需要配置对应数据源,使用Spring IOC容器管理JdbcTemplate对象。

java 复制代码
@Configuration
@ComponentScan
public class SpringConfig {
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("?");
        dataSource.setPassword("?");
        dataSource.setUrl("?");
        return dataSource;
    }

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

6.2 CURD操作

java 复制代码
@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate template;

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


    public void query0() {
        String sql = "select * from t_user";
        List<User> list = template.query(sql, (rs, rowNum) -> {
            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 query1() {
        String sql = "select * from t_user";
        List<User> list = template.query(sql, new BeanPropertyRowMapper<>(User.class));
        for (User user : list) {
            System.out.println(user);
        }
    }
}

07 Hibernate

Hibernate全称为Object Relative DateBase Mapping,建立Java对象与关系数据库之间映射,实现直接存取Java对象。

Apache DBUtils和Spring JdbcTemplate虽然简化数据库操作,但是提供的能力还是比较简单,缺少缓存、事务管理和日志打印等高阶管理支持,所以实际开发并非直接使用上述技术,而是使用Hibernate和MyBatis这些专业ORM持久层框架。

7.1 项目结构

java 复制代码
hibernate
|- src
   |-- main
       |-- java 
       |-- resources
|- build.gradle
|- setting.gradle

7.2 初始化配置

文件 文件路径
setting.gradle setting.gradle
properties 复制代码
rootProject.name = 'hibernate'
文件 文件路径
build.gradle build.gradle
groovy 复制代码
plugins {
    id 'java'
}

group 'net.feiyu'
version '1.0-SNAPSHOT'

dependencies {
    // spring boot pom
    implementation platform('org.springframework.boot:spring-boot-dependencies:2.3.10.RELEASE')
    
    // spring data jpa
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa'
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web'
    
    // mysql
    implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.11'
}
文件 文件路径
hibernate.cfg.xml resources/hibernate.cfg.xml
xml 复制代码
<?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://host:3306/hibernate?characterEncoding=utf8&serverTimezone=UTC
        </property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">root</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>
    </session-factory>
</hibernate-configuration>

7.3 XML映射

文件 文件路径
hibernate.cfg.xml resources/hibernate.cfg.xml
xml 复制代码
<?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>
        <mapping resource="User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
文件 文件路径
User.hbm.xml resources/User.hbm.xml
java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        '-//Hibernate/Hibernate Mapping DTD 3.0//EN'
        'http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd'>
<hibernate-mapping>
    <class name="com.feiyu.model.User" table="t_user">
        <id name="id"/>
        <property name="userName" column="user_name"/>
        <property name="realName" column="real_name"/>
    </class>
</hibernate-mapping>
java 复制代码
public class XmlMain {
    // Hibernate操作案例
    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();

        // O:对象
        User user = new User();
        user.setId(666);
        user.setUserName("hibernate-1");
        user.setRealName("持久层框架");

        // 保存
        session.save(user);
        // 提交事务
        transaction.commit();
        // 关闭Session
        session.close();
    }
}

7.4 注解映射

Spring Data JPA提供JpaRepository实现持久层框架统一封装,底层基于Hibernate进行处理。

文件 包路径
User.java com.feiyu.model
java 复制代码
@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;
}
文件 包路径
UserDao.java com.feiyu.dao.UserDao
java 复制代码
public interface UserDao extends JpaRepository<User, Integer> {
    
}

7.5 总结

特点 内容
优点 1. 根据数据库方言自动生成SQL,移植性好 2. 自动管理连接资源 3. 实现对象和关系型数据的完全映射,操作对象与操作数据库记录一致 4. 提供缓存机制
缺点 1. Hibernate API无法指定部分字段操作,比如getupdatesave方法 2. 自定生成SQL方式,如果要基于SQL做一些优化也是非常困难的。 3. 不支持动态SQL,比如无法基于表名、条件和参数实现自动生成SQL,也就我无法分表

08 MyBatis

MyBatis前身来自于ibatis,ibatis来源internet和abatis ['æbətɪs](障碍物)两个单词组合。2001开始研发ibatis,2004年捐赠给Apache,2010年正是更名为MyBatis。

MyBatis属于半自动ORM持久层框架,封装程度没有Hibernate那么高,解决Hibernate不能支持动态SQL和无法指定部分字段操作问题。主要添加自定义SQL、存储过程和高级映射支持,通过XML或者注解简单实现配置映射原始类型、接口和Java POJO(Plain Old Java Objects,普通Java对象)映射数据库记录,免除几乎所有JDBC代码、设置参数和获取结果集处理。

MyBatis官网地址:mybatis.org/mybatis-3/z...

特点 内容
优点 1. 使用连接池对连接进行管理 2. SQL和代码分离,集中管理 3. 结果集映射 4. 参数映射和动态SQL 5. 重复SQL的提取 6. 缓存管理 7. 插件机制

09 MyBatis-Plus

MyBatis Plus增强原生MyBatis打造的高阶工具,在原生MyBatis功能基础上附加plus特有功能。

MyBatis Plus功能 内容
通用CRUD 业务接口Mapper继承BaseMapper,无需编写任何接口方法与配置文件,即可获得通用增删改查功能
条件构造器 通过实体包装类EntityWrapper拼接SQL语句,支持排序、分组查询等复杂SQL
代码生成器 支持一系列策略配置与全局配置,相比MyBatis代码生成更好用

MyBatis Plus官方地址:mybatis.plus/guide

相关推荐
郝学胜-神的一滴2 小时前
SpringBoot实战指南:从快速入门到生产级部署(2025最新版)
java·spring boot·后端·程序人生
爷_6 小时前
字节跳动震撼开源Coze平台!手把手教你本地搭建AI智能体开发环境
前端·人工智能·后端
不过普通话一乙不改名10 小时前
第一章:Go语言基础入门之函数
开发语言·后端·golang
豌豆花下猫10 小时前
Python 潮流周刊#112:欢迎 AI 时代的编程新人
后端·python·ai
Electrolux11 小时前
你敢信,不会点算法没准你赛尔号都玩不明白
前端·后端·算法
whhhhhhhhhw11 小时前
Go语言-fmt包中Print、Println与Printf的区别
开发语言·后端·golang
ん贤11 小时前
Zap日志库指南
后端·go
Spliceㅤ12 小时前
Spring框架
java·服务器·后端·spring·servlet·java-ee·tomcat
IguoChan12 小时前
10. Redis Operator (3) —— 监控配置
后端
Micro麦可乐14 小时前
前端与 Spring Boot 后端无感 Token 刷新 - 从原理到全栈实践
前端·spring boot·后端·jwt·refresh token·无感token刷新