Spring数据访问-JDBC

Spring JDBC简介

数据是应用程序的血液,鉴于数据的重要地位,以健壮、简单和清晰的方式开发应用程序的数据访问部分就显得举足轻重了。

在Java中,JDBC是与关系型数据库交互的最基本方式。但是按照规范,JDBC有些太笨重了。Spring能够解除我们使用JDBC中的大多数痛苦,包括小出样板式代码、简化JDBC异常处理,你所需要做的仅仅是关注要执行的SQL语句。

在本章中,我们将学习Spring对数据持久化的支持,以及Spring为JDBC所提供的基于模板的抽象,它能够极大地简化JDBC的使用。

JDBC代码分析

让我们先来看看原始的符合JDBC规范的代码,以便于后面与Spring JDBC进行对比,看看Spring到底能如何简化我们的JDBC开发。以下是一段根据id查询Car记录的逻辑:

@Repository
public class CarDao {

    @Autowired
    private DataSource dataSource;

    public Car findById(int id){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        Car car = null;
        try {
            conn = dataSource.getConnection();//获取连接
            ps = conn.prepareStatement("select * from car where id=?");//预编译语句
            ps.setInt(1, id);// 绑定参数
            rs = ps.executeQuery();// 执行语句
            if(rs.next()){// 处理结果
                car = new Car();
                car.setId(rs.getInt("id"));
                car.setName(rs.getString("name"));
                car.setPrice(rs.getInt("price"));
                car.setType(rs.getString("type"));
            }
        } catch (SQLException e) {// 处理异常
            e.printStackTrace();
        } finally{// 清理资源
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } finally{
                    conn = null;
                }
            }
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } finally{
                    ps = null;
                }
            }
            if(rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } finally{
                    rs = null;
                }
            }
        }
        return car;
    }
}

观察以上代码,对比类似的新增、更新、删除等逻辑,我们可以描述出以下JDBC代码的特征:

  • 在四十来行代码中,真正操作数据的只有20%的代码,而80%的代码都是样板代码(创建连接、异常处理、清理资源等)
  • 对于SQLException,我们基本无法处理它(除开打印日志),并且要捕捉两次(操作数据一次,清理资源一次),但根据该异常特点,我们必须try-catch或者throw

Spring数据访问设计理念

了解完以上JDBC的缺陷以后,我们来看看Spring通过哪些途径改善这些状况。

  1. 面向接口编程

    从前面几章的学习中我们了解到,Spring的目标之一就是提倡我们能够遵循面向对象原则中的“针对接口编程”。Spring对数据访问的支持也不例外。

    应用需要从某种类型的数据库中读取和写入数据,为了避免持久化的逻辑分散到应用的各个组件中,最好将数据访问的功能放到一个或者多个专注于此项任务的组件中,这样的组件通常称为数据访问对象(data access object,DAO)。

    为了避免应用与特定的数据访问策略耦合在一起,编写良好的DAO应该以接口的方式对调用者暴露功能:

如图所示,服务对象通过接口来访问DAO,这样做会有几个好处:

  1. 它使得服务对象易于测试,因为它们不再与特定的数据访问实现绑定在一起。
  2. 数据访问层是以持久化技术无关的方式进行访问的,这样更便于我们灵活的切换持久化框架,比如后期可以选择JDBC、MyBatis、JPA等等,从而将这种变化对其它部分所带来的影响降低到最小程度。

通过以上的设计,Spring向我们传达这样一种理念:接口是实现松耦合代码的关键,并且应将其应用到程序的各个层,而不仅仅是持久层。

CarDao.java

public interface ICarDao {

    Car findById(Integer id);

    List<Car> findAll();

    void insert(Car car);

    void update(Car car);

    void delete(Integer id);
}
  1. Spring数据访问异常体系

    分析JDBC的异常,我们可以归纳出可能导致抛出SQLException的常见问题包括:

    • 应用程序无法链接数据库
    • 要执行的查询存在语法错误
    • 查询中所使用的表或列不存在
    • 试图插入或更新的数据违反了数据库约束

    SQLException的问题在于捕获到它的时候该如何处理,事实上,能够触发该异常的问题通常是不能在catch代码块中解决的。例如应用程序不能连接到数据库,这通常意味着应用不能使用了。类的,如果查询时出现了错误,那在运行时基本也是无能为力的。

    如果无法从SQLException中恢复,那为何还要强制捕获它呢?

    Spring JDBC提供的数据访问异常体系解决了这个问题。Spring为读取和写入数据库的几乎所有错误都提供了异常,并且这些重新封装的异常都继承自DataAccessException。这个异常的特殊之处就在于它是一个非检查型异常。换句话说,开发人员不用强制编写catch代码块了。

  2. 数据访问模板化

    针对之前JDBC开发中,我们需要大量重复性的编写样板代码的问题,Spring将数据访问过程中固定和可变的部分明确划分为两个不同的类:模板(template)和回调(callback)。模板管理过程中固定的部分,而回调处理自定义的数据访问代码。

针对不同的持久化平台,Spring提供了多个可选的模板。如果直接使用JDBC,可使

JdbcTemplate,如果使用MyBatis,可使用SqlSessionTemplate,如果使用JPA,可使

用JpaTemplate,等等。

自定义数据源

  1. 加入依赖
    <!-- MySQL数据库依赖 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.20.RELEASE</version>
    </dependency>
    
  2. 写一个类实现Datasource接口
    @Component
    public class MyDatasource implements DataSource {
        private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
        private final String url = "jdbc:mysql:///test";
        private final String user = "root";
        private final String password = "root";
        @Override
        public Connection getConnection() throws SQLException {
            Connection conn = threadLocal.get();
            if(conn == null || conn.isClosed()){
                conn = DriverManager.getConnection(url, user, password);
            }
            return conn;
        }
    }
    
  3. 配置到容器中
    @Configuration
    @ComponentScan
    public class SpringConfig {
        @Bean
        public DataSource myDataSource(){
            return new MyDatasource();
        }
    }
    
  4. 测试
    public class TestMain {
        public static void main(String[] args) throws SQLException {
            AnnotationConfigApplicationContext ctx =
                new AnnotationConfigApplicationContext(SpringConfig.class);
            DataSource myDatasource = (DataSource) ctx.getBean("myDatasource");
            System.out.println(myDatasource);
        }
    }
    

配置第三方数据源—DBCP2

  1. 加入依赖

    <!-- dbcp2数据源依赖 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-dbcp2</artifactId>
        <version>2.5.0</version>
    </dependency>
    
  2. 配置到容器中

    @Configuration
    @ComponentScan
    public class SpringConfig {
        @Bean
        public DataSource myDataSource(){
            return new MyDatasource();
        }
        @Bean
        public DataSource dbcpDataSource(){
            BasicDataSource ds = new BasicDataSource();
            ds.setUrl("jdbc:mysql:///test");
            ds.setUsername("root");
            ds.setPassword("root");
            ds.setInitialSize(5);//设置初始连接数
            ds.setMaxIdle(15);//设置最大空闲连接数
            ds.setMinIdle(2);//设置最小空闲连接数
            ds.setMaxTotal(8);//设置最大活动连接数
            ds.setMaxWaitMillis(10000);//设置获取一个连接的最大等待时间
            return ds;
        }
    }
    
  3. DBCP2数据源的常用属性:

    参数默认值描述
    initialSize0初始化连接:连接池启动时创建的初始化连接数量,1.2版本后支持
    maxActive8最大活动连接:连接池在同一时间能够分配的最大活动连接的数量, 如果设置为非正数则表示不限制
    maxIdle8最大空闲连接:连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制
    minIdle0最小空闲连接:连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接,如果设置为0则不创建
    maxWait无限最大等待时间:当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常,如果设置为-1表示无限等待
  4. 测试
    public class TestMain {
        public static void main(String[] args) throws SQLException {
            AnnotationConfigApplicationContext ctx =
                new AnnotationConfigApplicationContext(SpringConfig.class);
            DataSource dbcpDataSource = (DataSource) ctx.getBean("dbcpDataSource");
            System.out.println(dbcpDataSource);
        }
    }
    

配置JDBCTemplate

  1. 加入依赖
    <!-- Spring-jdbc依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.20.RELEASE</version>
    </dependency>
    
  2. 注入JdbcTemplate对象,并调用方法
    @Repository
    public class CarDaoImpl implements ICarDao {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Autowired
        private CarRowMapper carRowMapper;
        public Car findById(Integer id) {
            Car car = jdbcTemplate.queryForObject("select * from car where id=?",
                                                  carRowMapper, id);
            return car;
        }
        public List<Car> findAll() {
            List<Car> list = jdbcTemplate.query("select * from car", carRowMapper);
            return list;
        }
        public void insert(Car car) {
            jdbcTemplate.update("insert into car values(null,?,?,?)",
                                car.getName(),car.getType(),car.getPrice());
        }
        public void update(Car car) {
            jdbcTemplate.update("update car set name=?,type=?,price=? where id=?",
                                car.getName(),car.getType(),car.getPrice(),car.getId());
        }
        public void delete(Integer id) {
            jdbcTemplate.update("delete from car where id=?", id);
        }
    }
    
  3. 在配置类中,配置JdbcTemplate对象
    @Configuration
    @ComponentScan
    public class BeanConfig {
        ...
            @Bean
            public JdbcTemplate jdbcTemplate(DataSource dataSource) {
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            return jdbcTemplate;
        }
        ...
    
    }
    

    可以看到,此时的DAO实现已经变得非常清晰简单了,每个方法几乎就只有一句代码,传入要执行的SQL和参数,就能获取到结果。而这些SQL和参数就是被包含在一个回调中执行的。

    Spring JDBC中还提供了一个结果集的行映射器RowMapper,方便我们进行结果集到POJO的转换:

    @Component
    public class CarRowMapper implements RowMapper<Car>{
        public Car mapRow(ResultSet rs, int rowNum) throws
            SQLException {
            Car car = new Car();
            car.setId(rs.getInt("id"));
            car.setName(rs.getString("name"));
            car.setType(rs.getString("type"));
            car.setPrice(rs.getInt("price"));
            return car;
        }
    }
    
  4. 测试
    package com.tuling.springjdbc;
    import org.springframework.context.ApplicationContext;
    import
        org.springframework.context.annotation.AnnotationConfigApplicationContext;
    public class MainTest {
        public static void main(String[] args) {
            ApplicationContext ctx = new
                AnnotationConfigApplicationContext(BeanConfig.class);
            ICarDao carDao = ctx.getBean(ICarDao.class);
            //查询单个
            // Car car = carDao.findById(1);
            // System.out.println(car);
            //查询多个
            // List<Car> list = carDao.findAll();
            // System.out.println(list.toString());
            //添加汽车
            // Car car = new Car();
            // car.setName("奔驰ML350");
            // car.setType("SUV");
            // car.setPrice(800000);
            // carDao.insert(car);
            //修改汽车
            // Car car = new Car();
            // car.setId(2);
            // car.setName("奔驰ML350");
            // car.setType("SUV");
            // car.setPrice(1000000);
            // carDao.update(car);
            //删除汽车
            carDao.delete(2);
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
spring-boot-starter-jpa和spring-boot-starter-jdbcSpring Boot框架中用于数据访问的两个常用依赖库。它们之间的主要区别如下: 1. 功能:spring-boot-starter-jpa是用于支持Java持久化API(JPA)的依赖库,它提供了对关系型数据库的对象关系映射(ORM)支持。而spring-boot-starter-jdbc是用于支持Java数据库连接(JDBC)的依赖库,它提供了对关系型数据库的基本操作支持。 2. 抽象层级:spring-boot-starter-jpa在JPA之上提供了更高级别的抽象,通过使用实体类和注解来映射数据库表和实体之间的关系。它隐藏了底层数据库的细节,使开发者可以更专注于业务逻辑的实现。而spring-boot-starter-jdbc则直接使用JDBC API进行数据库操作,需要开发者手动编写SQL语句。 3. 配置方式:spring-boot-starter-jpa需要配置数据源和JPA相关的属性,如数据库连接信息、实体类扫描路径、事务管理等。而spring-boot-starter-jdbc只需要配置数据源相关的属性,如数据库连接信息即可。 4. 使用场景:如果你的应用程序需要进行复杂的对象关系映射和查询操作,或者需要使用JPA提供的高级特性(如缓存、延迟加载等),那么可以选择使用spring-boot-starter-jpa。而如果你只需要进行简单的数据库操作,或者对数据库的操作更加灵活,可以选择使用spring-boot-starter-jdbc。 总结来说,spring-boot-starter-jpa适用于需要进行对象关系映射和高级查询的场景,而spring-boot-starter-jdbc适用于简单的数据库操作和对数据库的直接控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值