欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring JDBC的使用详解

程序员文章站 2022-04-11 12:08:02
jdbc介绍从这篇文章开始,我们将会介绍springboot另外一个核心的技术,即数据库访问技术,提到数据访问,学习java的同学瞬间能就想起jdbc技术,jdbc 是 java database c...

jdbc介绍

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

Spring JDBC的使用详解

这样设计的好处,就是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的使用的资料请关注其它相关文章!

相关标签: Spring JDBC