Spring JDBC的使用详解

发布时间:2021-07-17 21:51 来源:脚本之家 阅读:0 作者:黑色的灯塔 栏目: 编程语言 欢迎投稿:712375056

目录

        JDBC介绍

        从这篇文章开始,我们将会介绍SpringBoot另外一个核心的技术,即数据访问技术,提到数据访问,学习Java的同学瞬间能就想起JDBC技术,JDBC 是 Java Database Connectivity 的全称,是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的一套标准的API,这套标准不同的数据库厂家之间共同准守,并提供各自的具体实现。如图所示:

        这样设计的好处,就是Java程序只需要和JDBC API交互,从而屏蔽了访问数据库的复杂的实现,大大降低了Java程序访问数据库的复杂度。对于日常开发而言,我们只需要掌握JDBC API 规范中的几个核心编程对象即可,这些对象包括DriverManger、Connection、Statement及ResultSet。

        DriverManager

        DriverManager主要负责加载不同数据库厂家提供的驱动程序包(Driver),并且根据不同的请求向Java程序返回数据库连接(Connection)对象,先看下Driver接口的定义:

        public interface Driver {
            //获取数据库连接
            Connection connect(String url, java.util.Properties info)
                throws SQLException;
            boolean acceptsURL(String url) throws SQLException;
            DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
                                 throws SQLException;
            int getMajorVersion();
            int getMinorVersion();
            boolean jdbcCompliant();
            public Logger getParentLogger() throws SQLFeatureNotSupportedException;
        }
        

        Driver中有个重要的方法 connect,来提供Connection对象

        不同的数据库对Driver,有具体的实现,以MySql为例:

        public class Driver extends NonRegisteringDriver implements java.sql.Driver {
            // 通过 DriverManager 注册 Driver
            static {
                try {
                    java.sql.DriverManager.registerDriver(new Driver());
                } catch (SQLException E) {
                    throw new RuntimeException("Can't register driver!");
                }
        	}
        	…
        }
        

        这里用到了DriverManager,DriverManager通过 registerDriver来注册不同数据库的Driver,并且还提供了getConnection返回数据库连接对象。

        Connection

        通过DriverManager可以获取Connetion对象,Connection对象可以理解与数据库连接的一种会话(Session),一个Connection对象代表一个数据库的连接,负责完成与数据库底层的通讯。

        Connection对象提供了一组重载的方法来创建Statement和PreparedStatement,Statement和PreparedStatement是SQL执行的载体,另外Connection对象还会涉及事务相关的操作。

        Connection对象最核心的几个方法如下:

        public interface Connection  extends Wrapper, AutoCloseable {
        	//创建 Statement
        	Statement createStatement() throws SQLException;
        	//创建 PreparedStatement
        	PreparedStatement prepareStatement(String sql) throws SQLException;
        	//提交
        	void commit() throws SQLException;
        	//回滚
        	void rollback() throws SQLException;
        	//关闭连接
        	void close() throws SQLException;
        }
        

        Statement/PreparedStatement

        Statement和PreparedStatement是由Connection对象来创建的,用来执行静态的SQL语句并且返回生成的结果集对象,这里存在两种类型,一种是普通的Statement,另外一种支持预编译的PreparedStatement。

        所谓预编译,是指数据库的编译器会对 SQL 语句提前编译,然后将预编译的结果缓存到数据库中,下次执行时就可以通过替换参数并直接使用编译过的语句,从而大大提高 SQL 的执行效率。

        以Statement为例,看下Statement最核心的方法:

        public interface Statement extends Wrapper, AutoCloseable {
        	//执行查询语句
        	ResultSet executeQuery(String sql) throws SQLException; 
        	//执行更新语句
        	int executeUpdate(String sql) throws SQLException; 
        	//执行 SQL 语句
        	boolean execute(String sql) throws SQLException; 
        	//执行批处理
            int[] executeBatch() throws SQLException;
        }
        

        ResultSet

        通过Statement或PreparedStatement执行SQL语句,我们引出了另外一个对象即为ResultSet对象,代码如下:

        public interface ResultSet extends Wrapper, AutoCloseable {
        	//获取下一个结果
        	boolean next() throws SQLException;
        	//获取某一个类型的结果值
        	Value getXXX(int columnIndex) throws SQLException;
        	…
        }
        

        ResultSet对象提供了next()方法,用来对整个结果集遍历操作,如果next()方法返回为true,说明还有下一条记录,

        我们可以调用 ResultSet 对象的一系列 getXXX() 方法来取得对应的结果值。

        JDBC访问数据库流程

        对于开发人员而言,通过JDBC的API是Java访问数据库的主要途径,下面用代码来展示下访问数据库的一个整体流程:

        String url = "jdbc:mysql://localhost:3306/test" ;
        String username = "root" ;
        String password = "root" ;
        
        //1.通过DriverManager获取connection连接
        Connection connection = DriverManager.getConnection(url,username,password);
        
        //2.创建preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement("select * from user");
        
        //3.执行SQL返回ResultSet
        ResultSet resultSet = preparedStatement.executeQuery();
        
        //4.遍历resultSet结果集
        while (resultSet.next()){
            //resultSet.getString("1");
        }
        
        //5.释放资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
        

        配置数据源

        上面我们在介绍JDBC的时候,Connection对象是通过DriverManager获取,Connection对象代表着和数据库的连接,每次通过DriverManager获取比较耗时,影响了系统的性能。那有没有办法能够复用Connection对象呢,答案是肯定的

        JDBC给我们提供了DataSource接口来实现Connection的复用,核心代码如下:

        public interface DataSource  extends CommonDataSource, Wrapper {
         
          Connection getConnection() throws SQLException;
         
          Connection getConnection(String username, String password)
            throws SQLException;
        }
        

        作为一种基础组件,不需要开发人员自己实现 DataSource,因为业界已经存在了很多优秀的实现方案,如 DBCP、C3P0 、Druid 、Hikari等

        SpringBoot默认HikariDataSource作为DataSource的实现,现在我们SpringBoot为例,看下SpringBoot如何通过JDBC来操作数据库的,在进行数据库操作之前,我们首先需要先配置DataSource,SpringBoot配置DataSource非常简单,只需要在配置文件中添加DataSource的配置:

        spring:
          # datasource 数据源配置内容
          datasource:
            url: jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8
            driver-class-name: com.mysql.cj.jdbc.Driver
            username: root
            password: root
        

        使用JDBC操纵数据库

        DataSource配好后,我们在本地的数据库服务中,创建一个test数据库,并且执行以下DDL创建user表

        CREATE TABLE `user` (
          `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
          `username` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
          `password` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码',
          `create_time` datetime DEFAULT NULL COMMENT '创建时间',
          PRIMARY KEY (`id`),
          UNIQUE KEY `idx_username` (`username`)
        ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
        

        接下来我们创建一个实体类,实体类中属性和user表中字段一一对应

        @Data
        public class User {
            /**
             * 主键
             */
            private Integer id;
            /**
             * 用户名
             */
            private String username;
            /**
             * 密码
             */
            private String password;
            /**
             * 创建时间
             */
            private Date createTime;
        
        }
        

        注意:这里使用了Lombok的@Data注解来生成get/set方法。

        我们再定义一个UserDao接口,

        public interface UserDao {
            /**
             *  新增
             * @param user
             * @return
             */
            Integer insert(User user);
            /**
             *  根据ID查询
             * @param id
             * @return
             */
            User selectById(Integer id);
            /**
             *  根据ID更新
             * @param user
             * @return
             */
            Integer updateById(User user);
            /**
             *  根据ID删除
             * @param id
             * @return
             */
            Integer deleteById(Integer id);
        }
        

        这里之所以要抽离出一个UserDao一层有两个原因:第一UserDao只封装了对use表的数据库操作,代码易于维护和管理,第二我们可以基于UserDao接口提供不同的实现来访问数据库,比如我们可以提供基于原生JDBC的实现,也可以用JDBCTemplate实现数据库的访问,还可以通过Mybatis等

        接下来将通过代码形式来展示下SpringBoot是如何通过JDBC API对数据库进行CRUD操作的。我们来定义UserDao的具体实现类命名为:UserRawJdbcDao实现以下方法:

        新增数据

        @Override
            public Integer insert(User user) {
              	final String SQL_INSERT = "INSERT INTO user(username, password, create_time) VALUES(?, ?, ?)";
                Connection connection = null;
                PreparedStatement statement = null;
                ResultSet rs = null;
                Integer count = 0;
                try{
                    connection = dataSource.getConnection();
                    statement = connection.prepareStatement(SQL_INSERT, Statement.RETURN_GENERATED_KEYS);
                    statement.setString(1,user.getUsername());
                    statement.setString(2,user.getPassword());
                    statement.setTimestamp(3,new Timestamp(user.getCreateTime().getTime()));
                    count = statement.executeUpdate();
                    rs = statement.getGeneratedKeys();
                    if(rs.next()){
                        user.setId(rs.getInt(1));
                    }
                }catch (SQLException e){
                    e.printStackTrace();
                }finally {
                    try {
                        if(rs != null){
                            rs.close();
                        }
                        if(statement != null){
                            statement.close();
                        }
                        if(connection != null){
                            connection.close();
                        }
        
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
        
                }
                return count;
            }
        

        查询数据

        @Override
            public User selectById(Integer id) {
                final String SQL_SELECT_ID = "SELECT id,username,password,create_time FROM user WHERE id = ?";
                Connection connection = null;
                PreparedStatement statement = null;
                ResultSet rs = null;
                User user = null;
                try{
                    connection = dataSource.getConnection();
                    statement = connection.prepareStatement(SQL_SELECT_ID);
                    statement.setInt(1, id);
                    rs = statement.executeQuery();
                    if(rs.next()){
                        user = new User();
                        user.setId(rs.getInt("id"));
                        user.setUsername(rs.getString("username"));
                        user.setPassword(rs.getString("password"));
                        user.setCreateTime(rs.getTimestamp("create_time"));
                    }
                }catch (SQLException e){
                    e.printStackTrace();
                }finally {
                    try {
                        if(rs != null){
                            rs.close();
                        }
                        if(statement != null){
                            statement.close();
                        }
                        if(connection != null){
                            connection.close();
                        }
        
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                return user;
            }
        

        更新数据

        @Override
            public Integer updateById(User user) {
                final String SQL_UPDATE = "UPDATE user SET username = ?, password = ? WHERE id = ?";
                Connection connection = null;
                PreparedStatement statement = null;
                ResultSet rs = null;
                Integer count = 0;
        
                try{
                    connection = dataSource.getConnection();
                    statement = connection.prepareStatement(SQL_UPDATE);
                    statement.setString(1,user.getUsername());
                    statement.setString(2,user.getPassword());
                    statement.setInt(3,user.getId());
                    count = statement.executeUpdate();
                }catch (SQLException e){
                    e.printStackTrace();
                }finally {
                    try {
                        if(rs != null){
                            rs.close();
                        }
                        if(statement != null){
                            statement.close();
                        }
                        if(connection != null){
                            connection.close();
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                return count;
            }
        

        删除数据

        @Override
            public Integer deleteById(Integer id) {
                final String SQL_DELETE = "DELETE FROM user WHERE id = ?";
                Connection connection = null;
                PreparedStatement statement = null;
                ResultSet rs = null;
                Integer count = 0;
                try{
                    connection = dataSource.getConnection();
                    statement = connection.prepareStatement(SQL_DELETE);
                    statement.setInt(1,id);
                    count = statement.executeUpdate();
                }catch (SQLException e){
                    e.printStackTrace();
                }finally {
                    try {
                        if(rs != null){
                            rs.close();
                        }
                        if(statement != null){
                            statement.close();
                        }
                        if(connection != null){
                            connection.close();
                        }
        
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                return count;
            }
        

        到此,SpringBoot通过调用原生的JDBC的API完成对user表的CRUD操作,这代码对有代码洁癖的同学简直不能忍,有大量共性的代码,如创建Connection、Statement、ResultSet、资源的释放和异常的处理。这部分封装和优化SpringBoot已经处理过了,SpringBoot提供了JdbcTemplate模板工具类实现数据访问,它简化了JDBC API的使用方法。

        使用JdbcTemplate操纵数据库

        同UserRawJdbcDao,我们再定义UserDao的另外一套实现类命名为:UserJdbcDao,这套实现类是通过JdbcTemplate完成对数据库的操作,完成接口定义的方法如下:

        新增数据

         
        @Override
        public Integer insert(User user){
          
            // 创建 KeyHolder 对象,设置返回的主键 ID
            KeyHolder keyHolder = new GeneratedKeyHolder();
            int count = jdbcTemplate.update(INSERT_PREPARED_STATEMENT_CREATOR_FACTORY.newPreparedStatementCreator(
                    Arrays.asList(user.getUsername(),user.getPassword(),user.getCreateTime())),keyHolder);
            // 设置 ID 主键到 entity 实体中
            if (keyHolder.getKey() != null) {
                user.setId(keyHolder.getKey().intValue());
            }
            // 返回影响行数
            return count;
        }
        

        查询数据

          @Override
            public User selectById(Integer id){
                User result = jdbcTemplate.queryForObject("SELECT id, username, password, create_time FROM user WHERE id=?",
                        new BeanPropertyRowMapper<>(User.class), id);
                return result;
            }
        

        更新数据

          @Override
            public Integer updateById(User user) {
                return jdbcTemplate.update("UPDATE user SET username = ?, password = ? WHERE id = ?",
                        user.getUsername(),user.getPassword(),user.getId());
            }
        

        删除数据

        @Override
            public Integer deleteById(Integer id){
                return jdbcTemplate.update("DELETE FROM user WHERE id = ?", id);
            }
        

        小结

        通过对比我们发现使用JdbcTemplate模板工具类可以大大减少JDBC访问数据库的代码复杂度,作为开发人员我们应该只关心业务逻辑的具体实现过程,对JDBC底层对象的创建,资源的释放,异常的捕获,应该交给框架统一维护和管理。

        虽然JdbcTemplate减少的我们访问数据库的代码量,不过使用也有一些问题,比如:新增数据的时候默认无法返回生成主键的id,将SQL硬编码到Java代码中,如果SQL修改,需要重新编译Java代码,不利于系统的维护等。这时我们需要另外一个框架,它就是大名鼎鼎的Mybatis,下一篇我将会介绍SpringBoot如何整合Mybatis。

        项目源码

        以上就是Spring JDBC的使用详解的详细内容,更多关于Spring JDBC的使用的资料请关注脚本之家其它相关文章!

        免责声明:本站发布的内容(图片、视频和文字)以原创、来自本网站内容采集于网络互联网转载等其它媒体和分享为主,内容观点不代表本网站立场,如侵犯了原作者的版权,请告知一经查实,将立刻删除涉嫌侵权内容,联系我们QQ:712375056,同时欢迎投稿传递力量。