• 20191105 《Spring5高级编程》笔记-第6章


    第6章 Spring JDBC支持

    Spring官方:

    位于Spring Framework Project下。
    文档:
    https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#jdbc

    MySQL通常更广泛地用于Web应用程序开发,特别是Linux平台上。PostgreSQL对Oracle开发人员更友好,因为它的过程语言PLpgSQL非常接近Oracle的PL/SQL语言。

    6.1 介绍Lambda表达式

    大多数使用了模板或回调的Spring API都可以使用lambda表达式,不限于JDBC。

    6.3 研究JDBC基础结构

    JDBC为Java应用程序访问存储在数据库中的数据提供了一种标准方式。JDBC基础结构的核心是针对每个数据库的驱动程序,即允许Java代码访问数据库的驱动程序。
    一旦加载驱动程序,就会注册java.sql.DriverManager类。该类管理驱动程序列表并提供建立与数据库连接的静态方法。DriverManager.getConnection()方法返回驱动程序实现的java.sql.Connection接口。该接口允许针对数据库运行SQL语句。

    连接(Connection)是一种稀缺资源,建立起来非常昂贵。

    演示怎么用JDBC写DAO代码。

    6.4 Spring JDBC基础结构

    org.springframework:spring-jdbc 提供对JDBC的支持,分为5个部分:
    image

    6.5 数据库连接和数据源

    javax.sql.DataSource 用来帮助管理数据库连接。DataSourceConnection之间的区别在于DataSource可以提供并管理Connection。

    org.springframework.jdbc.datasource.DriverManagerDataSourceDataSource的最简单实现,通过调用DriverManager来获得连接,不支持数据库连接池。

    6.6 嵌入数据库支持

    Spring提供了嵌入式数据库支持,该支持会自动启动嵌入式数据库并将其作为应用程序的DataSource公开。

    Spring支持HSQL(默认)、H2DERBY

    以H2为例:

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
    
    @Configuration
    public class EmbeddedJdbcConfig {
        @Bean
        public DataSource dataSource() {
            EmbeddedDatabaseBuilder dbBuilder = new EmbeddedDatabaseBuilder();
            return dbBuilder.setType(EmbeddedDatabaseType.H2).addScripts("classpath:db/h2/schema.sql", "classpath:db/h2/test_data.sql").build();
        }
    }
    

    这里要注意脚本的顺序,DDL文件应该第一个显示,之后是DML文件。

    # schema.sql
    CREATE TABLE SINGER
    (
        ID         INT         NOT NULL AUTO_INCREMENT,
        FIRST_NAME VARCHAR(60) NOT NULL,
        LAST_NAME  VARCHAR(40) NOT NULL,
        BIRTH_DATE DATE,
        UNIQUE UQ_SINGER_1 (FIRST_NAME, LAST_NAME),
        PRIMARY KEY (ID)
    );
    
    CREATE TABLE ALBUM
    (
        ID           INT          NOT NULL AUTO_INCREMENT,
        SINGER_ID    INT          NOT NULL,
        TITLE        VARCHAR(100) NOT NULL,
        RELEASE_DATE DATE,
        UNIQUE UQ_SINGER_ALBUM_1 (SINGER_ID, TITLE),
        PRIMARY KEY (ID),
        CONSTRAINT FK_ALBUM FOREIGN KEY (SINGER_ID) REFERENCES SINGER (ID)
    );
    
    -- test_data.sql
    INSERT INTO `singer`(`id`, `first_name`, `last_name`, `birth_date`) VALUES (1, 'John', 'Mayer', '1997-10-16');
    INSERT INTO `singer`(`id`, `first_name`, `last_name`, `birth_date`) VALUES (2, 'Eric', 'Clapton', '1945-03-30');
    INSERT INTO `singer`(`id`, `first_name`, `last_name`, `birth_date`) VALUES (3, 'John', 'Butler', '1975-04-01');
    
    INSERT INTO `album`(`id`, `singer_id`, `title`, `release_date`) VALUES (1, 1, 'The Search For Everything', '2017-01-20');
    INSERT INTO `album`(`id`, `singer_id`, `title`, `release_date`) VALUES (2, 1, 'Battle Studies', '2009-11-17');
    INSERT INTO `album`(`id`, `singer_id`, `title`, `release_date`) VALUES (3, 2, 'From the Cradle', '1994-09-13');
    
    public class DbConfigTest {
        @Test
        public void test3() {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
            ctx.register(EmbeddedJdbcConfig.class);
            ctx.refresh();
    
            DataSource dataSource = ctx.getBean("dataSource", DataSource.class);
            testDataSource(dataSource);
            ctx.close();
        }
    
        private void testDataSource(DataSource dataSource) {
            Connection connection = null;
            try {
                connection = dataSource.getConnection();
                PreparedStatement statement = connection.prepareStatement("select 1");
                ResultSet resultSet = statement.executeQuery();
                while (resultSet.next()) {
                    int mockVal = resultSet.getInt("1");
                    System.out.println("mockVal = " + mockVal);
                }
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    对于本地开发或单元测试来说,嵌入式数据库支持是非常有用的。

    6.7 在DAO类中使用DataSource

    数据访问对象(DAO)模式用于将低级数据访问API或操作与高级业务服务相分离。数据访问对象模式需要以下组件:

    • DAO接口:该接口定义了在模型对象(或多个对象)上执行的标准操作;
    • DAO实现:该类提供了DAO接口的具体实现。通常使用JDBC连接或数据源来处理模型对象;
    • 模型对象也称为数据对象或实体:这是映射到数据表记录的简单POJO;

    6.8 异常处理

    Spring提倡使用运行时异常(非检查型异常)而不是检查型异常,Spring的SQL异常更精细。

    org.springframework.jdbc.support.SQLExceptionTranslator 接口负责将通用SQL错误代码转换为Spring JDBC异常。需要配合org.springframework.jdbc.core.JdbcTemplate使用。

    6.9 JdbcTemplate类

    该类代表Spring JDBC支持的核心。它可以执行所有类型的SQL语句,包括DDL和DML。

    JdbcTemplate类允许向数据库发出任何类型的SQL语句并返回任何类型的结果。

    6.9.1 在DAO类中初始化JdbcTemplate

    JdbcTemplate是线程安全的。这意味着可以选择在Spring的配置中初始化一个JdbcTemplate实例,并将其注入到所有的DAO bean中。

    // 定义jdbcTemplate和DAO
    @Bean
    public JdbcTemplate jdbcTemplate() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource());
        return jdbcTemplate;
    }
    
    @Bean
    public SingerDao singerDao() {
        JdbcSingerDao dao = new JdbcSingerDao();
        dao.setJdbcTemplate(jdbcTemplate());
        return dao;
    }
    
    // 使用jdbcTemplate
    @Override
    public String findNameById(Long id) {
        String name = jdbcTemplate.queryForObject("select first_name || ' ' || last_name from singer where id = ?", new Object[]{id}, String.class);
        return name;
    }
    

    6.9.2 通过NamedParameterJdbcTemplate使用命名参数

    org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate 提供了对命名参数的关系。与 org.springframework.jdbc.core.JdbcTemplate 没有继承关系,内部包含一个JdbcTemplate

    // 定义namedParameterJdbcTemplate和DAO
    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
        NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource());
        return namedParameterJdbcTemplate;
    }
    
    @Bean
    public NamedJdbcSingerDao namedJdbcSingerDao(){
        NamedJdbcSingerDao namedJdbcSingerDao = new NamedJdbcSingerDao();
        namedJdbcSingerDao.setNamedParameterJdbcTemplate(namedParameterJdbcTemplate());
        return namedJdbcSingerDao;
    }
    
    // 使用namedParameterJdbcTemplate
    @Override
    public String findNameById(Long id) {
        String sql = "select first_name || ' ' || last_name from singer where id = :singerId";
    
        Map<String, Object> namedParams = new HashMap<>();
        namedParams.put("singerId", id);
    
        return namedParameterJdbcTemplate.queryForObject(sql, namedParams, String.class);
    }
    

    6.9.3 使用RowMapper检索域对象

    Spring的 org.springframework.jdbc.core.RowMapper<T> 提供了一种简单的方法来完成从JDBC结果集到POJO的映射。

    // 手动创建RowMapper
    @Override
    public List<Singer> findAll() {
        String sql = "select id, first_name, last_name, birth_date from singer";
        return namedParameterJdbcTemplate.query(sql, new SingerMapper());
    }
    
    private class SingerMapper implements RowMapper<Singer> {
        @Override
        public Singer mapRow(ResultSet rs, int rowNum) throws SQLException {
            return new Singer().setId(rs.getLong("id")).setFirstName(rs.getString("first_name")).setLastName(rs.getString("last_name")).setBirthDate(rs.getDate("birth_date"));
        }
    }
    
    // 使用Lambda表达式创建匿名RowMapper
    @Override
    public List<Singer> findAll() {
        String sql = "select id, first_name, last_name, birth_date from singer";
        return namedParameterJdbcTemplate.query(sql, (rs, rowNum) ->
                new Singer().setId(rs.getLong("id")).setFirstName(rs.getString("first_name")).setLastName(rs.getString("last_name")).setBirthDate(rs.getDate("birth_date"))
        );
    }
    

    6.10 使用ResultSetExtractor检索嵌套域对象

    org.springframework.jdbc.core.RowMapper<T> 仅适用于将行映射到单个域对象;对于更复杂的对象结构,则需要使用org.springframework.jdbc.core.ResultSetExtractor 接口。

    public List<Singer> findAllWithAlbums() {
            String sql = "select s.id, s.first_name, s.last_name, s.birth_date" +
                    ", a.id as album_id, a.title, a.release_date " +
                    " from singer s left join album a on s.id = a.singer_id";
    //        return namedParameterJdbcTemplate.query(sql, new SingerWithDetailExtractor());
            return namedParameterJdbcTemplate.query(sql, rs -> {
                Map<Long, Singer> map = new HashMap<>();
                Singer singer;
                while (rs.next()) {
                    Long id = rs.getLong("id");
                    singer = map.get(id);
                    if (singer == null) {
                        singer = new Singer();
                        singer.setId(id);
                        singer.setFirstName(rs.getString("first_name"));
                        singer.setLastName(rs.getString("last_name"));
                        singer.setBirthDate(rs.getDate("birth_date"));
                        singer.setAlbums(new ArrayList<>());
                        map.put(id, singer);
                    }
    
                    Long albumId = rs.getLong("album_id");
                    if (albumId > 0) {
                        Album album = new Album();
                        album.setId(albumId);
                        album.setSingerId(id);
                        album.setTitle(rs.getString("title"));
                        album.setReleaseDate(rs.getDate("release_date"));
                        singer.addAlbum(album);
                    }
                }
                return new ArrayList<>(map.values());
            });
        }
    

    6.11 建模JDBC操作的Spring类

    Spring提供了许多有用的类来模拟JDBC数据库,从而让开发人员以更面向对象的方式将ResultSet中的查询和转换逻辑维护到域对象。

    • org.springframework.jdbc.object.MappingSqlQuery<T>
      允许将查询字符串和mapRow()方法一起封装到要给类中
    • org.springframework.jdbc.object.SqlUpdate
      能够封装任何SQL更新语句,绑定SQL参数,在插入新的记录后检索RDBMS生成的键等。
    • org.springframework.jdbc.object.BatchSqlUpdate
      允许执行批量更新操作。可以随时设置批量大小并刷新操作
    • org.springframework.jdbc.object.SqlFunction
      允许使用参数和返回类型调用数据库中的存储函数。此外,还可以使用另一个类StoredProcedure来帮助调用存储过程。

    6.12 使用MappingSqlQuery查询数据

    Spring提供了MappingSqlQuery<T>类对查询操作进行建模。

    示例:

    1. 查询不带参数:
    //----定义MappingSqlQuery-------------------------------------//
    public class SelectAllSingers extends MappingSqlQuery<Singer> {
        private static String SQL_SELECT_ALL_SINGER = "select id, first_name, last_name, birth_date from singer";
    
        public SelectAllSingers(DataSource ds) {
            super(ds, SQL_SELECT_ALL_SINGER);
        }
            
        @Override
        protected Singer mapRow(ResultSet rs, int rowNum) throws SQLException {
            Singer singer = new Singer();
            singer.setId(rs.getLong("id"));
            singer.setFirstName(rs.getString("first_name"));
            singer.setLastName(rs.getString("last_name"));
            singer.setBirthDate(rs.getDate("birth_date"));
            return singer;
        }
    }
    
    //----调用--------------------------------------------------//
    @Resource(name = "dataSource")
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
        this.selectAllSingers = new SelectAllSingers(dataSource);
    }
    @Override
    public List<Singer> findAll() {
        return selectAllSingers.execute();
    }
    
    1. 查询带参数:
    //----定义MappingSqlQuery-------------------------------------//
    public class SelectAllSingers extends MappingSqlQuery<Singer> {
    
        private static String SQL_FIND_BY_FIRST_NAME = "select id, first_name, last_name, birth_date from singer " +
                " where first_name = :first_name";
    
        public SelectAllSingers(DataSource ds) {
            super(ds, SQL_FIND_BY_FIRST_NAME);
            super.declareParameter(new SqlParameter("first_name", Types.VARCHAR));
        }
    
        @Override
        protected Singer mapRow(ResultSet rs, int rowNum) throws SQLException {
            Singer singer = new Singer();
            singer.setId(rs.getLong("id"));
            singer.setFirstName(rs.getString("first_name"));
            singer.setLastName(rs.getString("last_name"));
            singer.setBirthDate(rs.getDate("birth_date"));
            return singer;
        }
    }
    
    //----调用--------------------------------------------------//
    public List<Singer> findByFirstName(String firstName) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("first_name", firstName);
        return selectAllSingers.executeByNamedParam(paramMap);
    }
    

    MappingSqlQuery仅适用于将单个行映射到域对象。对于嵌套对象,则需要将JdbcTemplateResultSetExtractor一起使用。

    使用SqlUpdate更新数据

    //-------定义SqlUpdate-------------------//
    public class UpdateSinger extends SqlUpdate {
        private static String SQL_UPDATE_SINGER = "update singer set first_name=:first_name, last_name=:last_name, birth_date=:birth_date where id=:id";
    
        public UpdateSinger(DataSource ds) {
            super(ds, SQL_UPDATE_SINGER);
            super.declareParameter(new SqlParameter("first_name", Types.VARCHAR));
            super.declareParameter(new SqlParameter("last_name", Types.VARCHAR));
            super.declareParameter(new SqlParameter("birth_date", Types.DATE));
            super.declareParameter(new SqlParameter("id", Types.INTEGER));
        }
    }
    
    //-------调用-------------------//
    @Resource(name = "dataSource")
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
        this.updateSinger = new UpdateSinger(dataSource);
    }
    
    public void update(Singer singer) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("first_name", singer.getFirstName());
        paramMap.put("last_name", singer.getLastName());
        paramMap.put("birth_date", singer.getBirthDate());
        paramMap.put("id", singer.getId());
        updateSinger.updateByNamedParam(paramMap);
    }
    

    6.13 插入数据并检索生成的键

    从JDBC 3.0开始,允许以统一的方式检索RDBMS生成的键。

    //-----------定义SqlUpdate ------------------------//
    public class InsertSinger extends SqlUpdate {
        private static final String SQL_INSERT_SINGER = "insert into singer (first_name, last_name, birth_date) values " +
                " (:first_name, :last_name, :birth_date)";
    
        public InsertSinger(DataSource ds) {
            super(ds, SQL_INSERT_SINGER);
            super.declareParameter(new SqlParameter("first_name", Types.VARCHAR));
            super.declareParameter(new SqlParameter("last_name", Types.VARCHAR));
            super.declareParameter(new SqlParameter("birth_date", Types.DATE));
    
            super.setGeneratedKeysColumnNames(new String[]{"id"});
            super.setReturnGeneratedKeys(true);
        }
    }
    
    //-----------调用 ------------------------//
    public void insert(Singer singer) {
        System.out.println(singer);
    
        Map<String, Object> paramMap = Maps.newHashMap();
        paramMap.put("first_name", singer.getFirstName());
        paramMap.put("last_name", singer.getLastName());
        paramMap.put("birth_date", singer.getBirthDate());
    
        GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
        insertSinger.updateByNamedParam(paramMap, keyHolder);
        singer.setId(keyHolder.getKey().longValue());
    
        System.out.println(singer);
    }
    

    6.14 使用BatchSqlUpdate进行批处理操作

    //-----------定义BatchSqlUpdate ------------------------//
    public class InsertSingerAlbum extends BatchSqlUpdate {
        private static final String SQL_INSERT_SINGER_ALBUM = "insert into album (singer_id, title, release_date) values " +
                " (?, ?, ?)";
    
        private static final int BATCH_SIZE = 1;
    
        public InsertSingerAlbum(DataSource ds) {
            super(ds, SQL_INSERT_SINGER_ALBUM);
    
            declareParameter(new SqlParameter("singer_id", Types.VARCHAR));
            declareParameter(new SqlParameter("title", Types.VARCHAR));
            declareParameter(new SqlParameter("release_date", Types.DATE));
    
            setBatchSize(BATCH_SIZE);
        }
    }
    
    //-----------调用 ------------------------//
    @Override
        public void insertWithDetail(Singer singer) {
            Map<String, Object> paramMap = new HashMap<>();
            paramMap.put("first_name", singer.getFirstName());
            paramMap.put("last_name", singer.getLastName());
            paramMap.put("birth_date", singer.getBirthDate());
    
            GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
            insertSinger.updateByNamedParam(paramMap, keyHolder);
    
            singer.setId(keyHolder.getKey().longValue());
    
            List<Album> albums = singer.getAlbums();
            if (albums != null) {
                for (Album album : albums) {
                    insertSingerAlbum.update(new Object[]{singer.getId(), album.getTitle(), album.getReleaseDate()});
                }
            }
            insertSingerAlbum.flush();
        }
    

    6.15 使用SqlFunction调用存储函数

    //-----------定义SqlFunction------------------------//
    public class StoredFunctionFirstNameById extends SqlFunction<String> {
        private static final String SQL = "select getfirstnamebyid(?)";
    
        public StoredFunctionFirstNameById(DataSource dataSource){
            super(dataSource,SQL);
    
            declareParameter(new SqlParameter(Types.INTEGER));
            compile();
        }
    }
    
    //-----------调用------------------------//
    public String findFirstNameById(Long id) {
        List<String> result = storedFunctionFirstNameById.execute(id);
        return result.get(0);
    }
    

    Spring还提供了StoredProcedure来调用复杂的存储过程。

    6.16 Spring Data项目:JDBC Extensions

    Spring创建了Spring Data项目,主要目标是在Spring的核心数据访问功能之上提供有用的扩展,以便与传统RDBMS之外的数据库进行交互。

    Spring Data的扩展之一 JDBC Extensions 提供了一些高级功能:

    • QueryDSL支持;
    • 对Oracle数据库的高级支持;

    6.17 使用JDBC的注意事项

    在JDBC基础上有很多开源库,帮助缩小关系数据结构与Java的OO模型之间的差距。

    在使用Spring时,可以混合搭配不同的数据访问技术。例如,可以将Hibernate用作主ORM,然后将JDBC用作一些复杂查询逻辑或批处理操作的补充;可以在单个事务操作中将它们混合搭配,并封装在同一个事务中。

    6.18 Spring Boot JDBC

    Spring Boot JDBC的启动器:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    

    启动器使用 HikariCPcom.zaxxer.hikari.HikariDataSource 来配置DataSource bean。老版本可能使用其他不同的DataSource。

    Spring Boot还会自动注册以下bean:

    • JdbcTemplate
    • NamedParameterJdbcTemplate
    • PlatformTransactionManagerDataSourceTransactionManager

    Spring Boot对JDBC的默认配置:

    1. 启动器默认使用嵌入式数据库。默认schema.sql包含DDL语句,data.sql包含DML,可配置:
    // 默认位于classpath下
    spring:
      datasource:
        schema: classpath:db/h2/schema.sql
        data: classpath:db/h2/test_data.sqlspring.data
    
    1. 默认会在启动时初始化数据库,可通过spring.datasource.initialize = false来进行更改;
  • 相关阅读:
    PHP ceil() 函数
    PHP Array 函数
    php中的include()的使用技巧
    [观察者模式]在游戏开发中的应用
    [策略模式]在游戏开发中的应用
    使用EA将源码转化为类图
    PAT 1013 数素数 (20)
    PAT 1034 有理数四则运算(20)
    PAT 1033 旧键盘打字(20)
    PAT 1032 挖掘机技术哪家强(20)
  • 原文地址:https://www.cnblogs.com/huangwenjie/p/11797424.html
Copyright © 2020-2023  润新知