那些年背过的题:Spring jdbc设计与实现

192 阅读9分钟

Spring JDBC 是 Spring Framework 提供的一个模块,用于简化与关系数据库交互的工作。它提供了对 JDBC API 的封装,使得使用 JDBC 更为简便,同时减少了编写原始 JDBC 代码时需要处理的样板代码和异常处理逻辑。

Spring JDBC 的设计目标

  1. 简化数据库操作:通过模板方法模式,减少重复的 JDBC 代码。
  2. 自动资源管理:自动处理 ConnectionStatement 和 ResultSet 的资源释放,避免资源泄漏。
  3. 统一异常转换:将原生 SQL 异常转换为一致的 Spring DataAccessException 层次结构。
  4. 提供更高层次的抽象:例如 JdbcTemplate 和 NamedParameterJdbcTemplate,使得执行 SQL 语句更加方便。

核心组件

  1. JdbcTemplateJdbcTemplate 是 Spring JDBC 的核心类,它封装了基本的 JDBC 操作,如查询、插入、更新和删除。通过 JdbcTemplate,开发者可以避免手动管理资源和处理异常。

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public List<User> findAllUsers() {
        String sql = "SELECT * FROM users";
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
    }
    
  2. DataSourceDataSource 是一个标准的接口,用于获取数据库连接。Spring 提供了多种 DataSource 实现,例如 DriverManagerDataSourceDataSource 配置在应用服务器上如 Tomcat 或 JNDI。

    使用 Apache Commons DBCP:

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/mydb" />
        <property name="username" value="root" />
        <property name="password" value="password" />
    </bean>
    
  3. NamedParameterJdbcTemplateNamedParameterJdbcTemplateJdbcTemplate 的扩展,允许使用具名参数而不是传统的 ? 占位符,使得 SQL 语句更具可读性。

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    
    public void updateUser(User user) {
        String sql = "UPDATE users SET name = :name WHERE id = :id";
        Map<String, Object> params = new HashMap<>();
        params.put("name", user.getName());
        params.put("id", user.getId());
        namedParameterJdbcTemplate.update(sql, params);
    }
    
  4. RowMapperRowMapper 用于将结果集中的每一行映射到 Java 对象。Spring 提供了多个内置的实现,例如 BeanPropertyRowMapper,也可以自定义 RowMapper

    public class UserRowMapper implements RowMapper<User> {
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getInt("id"));
            user.setName(rs.getString("name"));
            return user;
        }
    }
    
    // 使用自定义的RowMapper
    public List<User> findAllUsers() {
        String sql = "SELECT * FROM users";
        return jdbcTemplate.query(sql, new UserRowMapper());
    }
    

使用示例

假设有一个用户表 users,包含字段 idname

  1. 配置数据源和 JdbcTemplate

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
            <property name="username" value="root"/>
            <property name="password" value="password"/>
        </bean>
    
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
    </beans>
    
  2. 使用 JdbcTemplate 进行数据库操作

    @Service
    public class UserService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        // 添加用户
        public void addUser(User user) {
            String sql = "INSERT INTO users (name) VALUES (?)";
            jdbcTemplate.update(sql, user.getName());
        }
    
        // 查询所有用户
        public List<User> findAllUsers() {
            String sql = "SELECT * FROM users";
            return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
        }
    
        // 根据ID查询用户
        public User findUserById(int id) {
            String sql = "SELECT * FROM users WHERE id = ?";
            return jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<>(User.class));
        }
    
        // 更新用户
        public void updateUser(User user) {
            String sql = "UPDATE users SET name = ? WHERE id = ?";
            jdbcTemplate.update(sql, user.getName(), user.getId());
        }
    
        // 删除用户
        public void deleteUser(int id) {
            String sql = "DELETE FROM users WHERE id = ?";
            jdbcTemplate.update(sql, id);
        }
    }
    
  3. 实体类 User

    public class User {
        private int id;
        private String name;
    
        // Getters and Setters
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

总结

Spring JDBC 通过提供模板类 (JdbcTemplateNamedParameterJdbcTemplate) 大大简化了与数据库交互的过程。这些模板类不仅减少了重复的样板代码,还提供了对资源管理和异常处理的自动化支持。同时,通过 RowMapper 接口,Spring JDBC 提供了一种灵活的方式将结果集映射到 Java 对象,使得数据库查询结果处理更加便捷。

JdbcTemplate核心源码实现流程

queryForObject 方法为例,这是一个常见的用于执行查询并返回单个结果的方法:

public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args) throws DataAccessException {
    return query(sql, new ResultSetExtractor<T>() {
        @Override
        public T extractData(ResultSet rs) throws SQLException, DataAccessException {
            if (!rs.next()) {
                throw new EmptyResultDataAccessException(1);
            }
            T result = rowMapper.mapRow(rs, 1);
            if (rs.next()) {
                throw new IncorrectResultSizeDataAccessException(1, 2);
            }
            return result;
        }
    }, args);
}

上述代码展示了一个 queryForObject 方法的实现,其中使用了 query 方法来实际执行 SQL 查询。

query 方法的核心实现

让我们深入到 query 方法的内部实现流程:

public <T> T query(String sql, ResultSetExtractor<T> rse, Object... args) throws DataAccessException {
    Assert.notNull(sql, "SQL must not be null");
    Assert.notNull(rse, "ResultSetExtractor must not be null");

    class QueryStatementCallback implements PreparedStatementCallback<T>, SqlProvider {
        @Override
        public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
            ResultSet rs = null;
            try {
                applyStatementSettings(ps);
                rs = ps.executeQuery();
                return rse.extractData(rs);
            }
            finally {
                JdbcUtils.closeResultSet(rs);
            }
        }
        @Override
        public String getSql() {
            return sql;
        }
    }

    return execute(new SimplePreparedStatementCreator(sql), new QueryStatementCallback());
}

execute 方法

execute 方法是 JdbcTemplate 执行核心逻辑的地方,它负责创建和管理数据库连接、预编译 SQL 语句、执行 SQL 以及处理资源释放。

public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException {
    Assert.notNull(psc, "PreparedStatementCreator must not be null");
    Assert.notNull(action, "PreparedStatementCallback must not be null");
    Connection con = DataSourceUtils.getConnection(getDataSource());
    PreparedStatement ps = null;
    try {
        ps = psc.createPreparedStatement(con);
        applyStatementSettings(ps);
        T result = action.doInPreparedStatement(ps);
        handleWarnings(ps);
        return result;
    }
    catch (SQLException ex) {
        // Exception translation
        throw translateException("PreparedStatementCallback", getSql(action), ex);
    }
    finally {
        JdbcUtils.closeStatement(ps);
        DataSourceUtils.releaseConnection(con, getDataSource());
    }
}

关键步骤解析

  1. 获取数据库连接: 使用 DataSourceUtils.getConnection 从数据源获取连接。这一步通常会涉及从连接池中获取连接。
  2. 创建 PreparedStatement: 调用 PreparedStatementCreator.createPreparedStatement 来创建 PreparedStatement 对象。
  3. 应用 Statement 设置: 调用 applyStatementSettings 配置 PreparedStatement(例如超时设置)。
  4. 执行 SQL 语句: 调用 PreparedStatementCallback.doInPreparedStatement 执行 SQL 语句。在这里,传入的 ResultSetExtractor 会处理 ResultSet 并返回结果。
  5. 处理 SQL 警告: 调用 handleWarnings 方法来处理可能出现的 SQL 警告。
  6. 异常处理: 捕获 SQLException 并通过 translateException 方法将其转换为 Spring 的 DataAccessException
  7. 资源清理: 在 finally 块中关闭 PreparedStatement 和释放数据库连接,确保资源不会泄漏。

总结

JdbcTemplate 的核心设计模式是模板方法模式,它通过抽象和回调的方式简化了 JDBC 操作。通过 execute 方法,JdbcTemplate 管理了数据库连接、SQL 执行和资源清理等复杂任务,使得开发者可以专注于业务逻辑而不必关心底层的 JDBC 细节。

Hibernate 底层如何实现将实体类映射成数据库表

以下是一个完整的概述,展示了从配置到执行的详细过程。

1. 配置与初始化

配置文件(hibernate.cfg.xml 或 hibernate.properties

Hibernate 的配置文件定义了数据库连接信息、Hibernate 属性和实体类映射信息。

<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mydb</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">password</property>

        <!-- Dialect -->
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

        <!-- Echo SQL to stdout -->
        <property name="hibernate.show_sql">true</property>

        <!-- Create the database schema on startup -->
        <property name="hibernate.hbm2ddl.auto">update</property>

        <!-- List of annotated classes -->
        <mapping class="com.example.model.User"/>
    </session-factory>
</hibernate-configuration>

2. 元数据解析

注解解析

Hibernate 使用 JPA 注解来解析实体类和它们之间的关系。这些注解提供了映射信息,将类和字段映射到数据库的表和列。

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "email", nullable = false, unique = true)
    private String email;

    // Getters and Setters
}

XML 映射解析

XML 文件也可以用于定义映射信息,这种方式在某些情况下可能比注解更加灵活。

<class name="com.example.model.User" table="users">
    <id name="id" column="id">
        <generator class="identity"/>
    </id>
    <property name="name" column="name" not-null="true"/>
    <property name="email" column="email" not-null="true" unique="true"/>
</class>

3. 配置处理与会话工厂建立

Configuration 类

该类负责加载配置文件和映射信息,并构建 SessionFactorySessionFactory 是一个重量级对象,通常在应用程序启动时创建一次。

Configuration configuration = new Configuration().configure();
SessionFactory sessionFactory = configuration.buildSessionFactory();

4. 实体类和表的映射

元数据处理器

通过 MetadataSourcesMetadataBuilder 配置和解析元数据。

ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
        .configure("hibernate.cfg.xml").build();

MetadataSources metadataSources = new MetadataSources(serviceRegistry);
metadataSources.addAnnotatedClass(User.class);

Metadata metadata = metadataSources.buildMetadata();
SessionFactory sessionFactory = metadata.buildSessionFactory();

5. SQL 生成与执行

Hibernate 方言

方言(Dialect)类生成特定于数据库类型的 SQL 语句。例如,MySQL 方言会生成 MySQL 特有的 SQL。

HQL 和 Criteria API

Hibernate 提供了 HQL 和 Criteria API,用于构建查询并将其转换为 SQL。

// HQL 示例
String hql = "FROM User WHERE email = :email";
Query<User> query = session.createQuery(hql, User.class);
query.setParameter("email", email);
List<User> results = query.list();

SQL 生成器

Hibernate 从实体元数据中提取表名、列名、主键等信息,生成相应的 SQL 语句。

String sql = "SELECT * FROM users WHERE email = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, email);
ResultSet rs = stmt.executeQuery();

6. 持久化上下文与缓存

一级缓存

每个 Hibernate 会话(Session)都有一级缓存,用于存储当前事务中的持久化对象,以减少重复查询。

Session session = sessionFactory.openSession();
User user = session.get(User.class, 1L);  // 第一次访问,从数据库加载
User sameUser = session.get(User.class, 1L);  // 第二次访问,从缓存加载

二级缓存

二级缓存是全局缓存,可以跨多个会话共享,以提高读取性能。Hibernate 支持集成各种二级缓存提供者,如 Ehcache、Infinispan 等。

<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

在实体类上启用二级缓存:

@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Table(name = "users")
public class User {
    // ...
}

7. 脏检查机制

Hibernate 使用脏检查机制(Dirty Checking)来自动检测实体对象的状态变化,并在事务提交时同步这些变化到数据库。这个机制可以确保持久化上下文中的所有更改都能自动反映到数据库中,而不需要显式地调用 save or update 方法。

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

User user = session.get(User.class, 1L);
user.setName("New Name");  // 修改对象

tx.commit();  // 提交事务时,Hibernate 自动检测并更新数据库
session.close();

8. 延迟加载

Hibernate 支持延迟加载,即在实际需要数据时才从数据库加载。这对于优化性能非常有用,尤其是在处理大型数据集时。

通过配置实体关联的 FetchType 来控制延迟加载:

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    // Getters and Setters
}

9. 事务管理

Hibernate 通常与 Java 事务 API (JTA) 或 Spring 事务管理集成,以管理事务的边界。例如,使用 JTA 可以使同一事务涉及多个资源(如数据库和消息队列)。

UserTransaction txn = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction");

txn.begin();
Session session = sessionFactory.getCurrentSession();

// 执行持久化操作
txn.commit();

在 Spring 中,可以通过注解方式进行事务管理:

@Service
public class UserService {

    @Autowired
    private SessionFactory sessionFactory;

    @Transactional
    public void createUser(User user) {
        Session session = sessionFactory.getCurrentSession();
        session.save(user);
    }
}

10. 实现总结

以下步骤总结了 Hibernate 将实体类映射成数据库表的核心过程:

  1. 配置初始化: 通过 hibernate.cfg.xml 或 hibernate.properties 文件配置数据库连接信息和映射类。
  2. 元数据解析: Hibernate 解析 JPA 注解或 XML 映射文件,生成元数据模型。
  3. 构建 SessionFactory: 使用 Configuration 和 ServiceRegistry 配置并构建 SessionFactory,这是一个重量级对象,通常在应用启动时创建一次。
  4. SQL 生成与执行: 基于方言(Dialect)生成特定数据库的 SQL 语句,并通过 JDBC 执行这些语句。
  5. 缓存机制: 使用一级缓存和可选的二级缓存来优化性能,减少数据库查询。
  6. 脏检查和自动同步: 使用脏检查机制自动检测实体对象的状态变化,并在事务提交时同步到数据库。
  7. 延迟加载和事务管理: 支持延迟加载和强大的事务管理机制,确保数据的一致性和完整性。

通过以上步骤和机制,Hibernate 实现了将 Java 实体类高效地映射成数据库表,同时提供了丰富的功能和灵活性来满足复杂业务场景的需求。