操作数据库方法总结1——JDBC、连接池与JDBCTemplate

本篇文章更好的阅读体验请见笔者的个人博客

引言

最近一直在操作数据库,然后就发现自己竟然只是会用Mybatis,知道JDBCTemplate,对于为什么要用它们以及它们的出现都是为了解决什么情景只能磕磕巴巴的说出个大概,所以想着把Java操作数据库的方法进行个系统的梳理,做个总结,希望一套下来自己能够清醒一点~

JDBC

简介

来说一下原始方法吧,JDBC的全称是Java Database Connectivity,意为Java和数据库的连接。设想这样一个场景,我们的Java程序需要连接各种各样的数据库,难道我们要为每个数据库都写一套连接管理代码吗,累死了好吧。JDBC是SUN公司提供的一套操作数据库的接口规范,定义了用来访问数据库的标准Java类库。为了开发方便,SUN公司提供了一套接口,让数据库厂商实现这些接口,程序员只需要使用这个接口就可以操作不同的数据库,不需要关注底层数据库驱动的安装,从而大大简化和加快了开发过程。

  • JDBC API:即面向应用的API,是一个抽象的接口,供应用程序开发人员使用,提供了程序到JDBC管理器的连接。
  • JDBC Driver API:即面向数据库驱动的API,需要开发商去实现这个接口,提供了JDBC管理器到数据库驱动程序的连接。
    我们要使用的主要就是下面四个核心类:
DriverManager(java.sql.DriverManager): 用于注册驱动,创建连接对象。
Connection接口(java.sql.Connection):     表示与数据库创建的连接。
Statement接口(java.sql.Statement):       执行数据库SQL语句,并返回相应结果的对象。
ResultSet接口(java.sql.ResultSet):       结果集或一张虚拟表,用于存储表数据的对象。

然后JDBC操作数据库的步骤如下:

  1. 加载驱动
  2. 创建数据库连接
  3. 创建sql语句并执行
  4. 处理执行结果
  5. 释放资源

实操

说了那么多,我们简单的上手一下,这个流程我相信大家肯定都灰常熟悉~

  1. 数据表,这里我们用的数据库是database_Learn创建一个表users
CREATE TABLE `users`(
	`id` INT    NOT NULL PRIMARY KEY AUTO_INCREMENT,
	`name` VARCHAR(150) DEFAULT NULL,
	`age` INT DEFAULT NULL,
	`gender` VARCHAR(10) DEFAULT NULL
);
INSERT INTO users(name, age, gender) VALUES ('张三', 18, '男'), ('李四', 21, '男'), ('玛奇玛', 27, '女');
  1. 创建一个maven项目,导入依赖
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
</dependencies>
  1. 创建一个测试类
public class JDBCTest {
    /**
     * 使用Jdbc访问数据库需要以下几步:
     * 1.加载驱动
     * 2.创建数据库连接
     * 3.创建sql语句并执行
     * 4.处理执行结果
     * 5.释放资源
     */
    public static void getUserInfo(){
        String URL = "jdbc:mysql://127.0.0.1:3306/database_Learn?characterEncoding=utf-8";
        String USER = "";
        String PASSWORD = "";
        Connection conn =null;
        PreparedStatement statement = null;
        ResultSet rs =null;

        try {
            // 1.加载驱动程序
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2.获得数据库链接
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
            // 3.通过数据库的连接操作数据库,实现增删改查(使用Statement类)
            String name="玛奇玛";
            String sql="select * from users where name=?";
            statement = conn.prepareStatement(sql);
            statement.setString(1, name);
            rs = statement.executeQuery();
            // 4.处理数据库的返回结果(使用ResultSet类)
            while (rs.next()) {
                System.out.println(rs.getInt("age") + " " + rs.getString("gender"));
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }catch (SQLException e) {
            e.printStackTrace();
        }finally{
            // 5.关闭资源
            if(conn!=null){
                try{
                    conn.close();
                    rs.close();
                    statement.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

    public static void chageUserInfo(){
        String URL = "jdbc:mysql://127.0.0.1:3306/database_Learn?characterEncoding=utf-8";
        String USER = "";
        String PASSWORD = "";
        Connection conn =null;
        PreparedStatement statement = null;

        try {
            // 1.加载驱动程序
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2.获得数据库链接
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
            // 3.通过数据库的连接操作数据库,实现增删改查(使用Statement类)
            String name="蕾塞";
            int age = 21;
            String gender = "女";
            String sql="insert into users(`name`,`age`,`gender`) values(?,?,?)";
            statement = conn.prepareStatement(sql);
            statement.setString(1, name);
            statement.setInt(2,age);
            statement.setString(3,gender);
            statement.executeUpdate();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }catch (SQLException e) {
            e.printStackTrace();
        }finally{
            // 5.关闭资源
            if(conn!=null){
                try{
                    conn.close();
                    statement.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }

    }

    public static void main(String[] args) {
        getUserInfo();
        chageUserInfo();
    }
}

然后你现在看到了,你每次操作数据库都要重复1-5这些步骤,而我们每次其实只有步骤3和步骤4是不同的,其他的都是重复步骤!这时候你可能会说,呵我用一个公共类或者公共方法把这些步骤封装一下不就行了?好,那我们来说说你的这个思路可能会有什么问题?

  1. 如果我把Connection设置为全局变量,每次我都使用同一个连接,那么你这个应用程序是打算永远都只会有一个人使用吗?如果请求多了,而这个Connection连接还没有处理完之前的请求是不是就要排队等待?
  2. ResultSet设置为全局总行了吧?不行。如果你每次请求都创建不同的Connection,而ResultSet用同一个,就会导致在多个请求的情况下有的请求的结果丢失!
  3. 这个原因当然是后话了,已经有框架帮我们解决重复建立和释放连接的问题了,你能保证你自己造的轮子比人家的好嘛?

JDBCTemplate

JDBCTemplateSpring提供访问JDBC的一个模板,目的是使JDBC更加易于使用。JDBCTemplate处理了资源的建立和释放,帮助我们避免一些常见的错误,比如忘了总要关闭连接,程序员只需要提供SQL语句和提取结果。
因为是Spring提供的,我知道大家用SpringBoot多,但是我还是想从Spring使用JDBCTemplate开始说起,因为我觉得这样会理解的更深刻一些。
那么我们先在maven项目中导入以下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
</dependencies>

初试

/**
* JDBCTemplate最基础的用法
*/
public class Demo1 {
    public static void main(String[] args) {
        // 准备数据源:spring的内置数据源
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://127.0.0.1:3306/database_Learn?characterEncoding=utf-8");
        ds.setUsername("root");
        ds.setPassword("cljcljclj215");
        // 1.创建JdbcTemplate对象
        JdbcTemplate jt = new JdbcTemplate();
        // 给jt设置数据源
        jt.setDataSource(ds);
        // 2.执行操作
        List<Map<String, Object>> list = jt.queryForList("SELECT * FROM users;");
        // 输出查询结果
        for (Map<String, Object> map : list) {
            System.out.println(map.get("name")+" "+map.get("age")+" "+map.get("gender"));
        }
    }
}

可以看到没有之前使用JDBC的时候那么多的代码,我们只需要创建JDBCTemplate然后给他个sql语句就行了,执行结果如下:

JDBCTemplate在Spring中的使用

基础使用

首先我们要在resources目录下新增一个testBean.xml文件来配置一下:

<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd">
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/database_Learn"/>
        <property name="username" value=""/>
        <property name="password" value=""/>
    </bean>
    <!-- 配置JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 向dataSource注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

然后我们想要使用的时候就可以这样:

public class Demo2 {
    public static void main(String[] args) {
        // 1. 获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("testBean.xml");
        // 2. 获取对象
        JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
        // 3. 执行操作
        List<Map<String, Object>> maps = jt.queryForList("SELECT * FROM `users`");
        // 输出
        for (Map<String, Object> map : maps) {
            System.out.println(map.get("name")+" "+map.get("age")+" "+map.get("gender"));
        }
    }
}

应该不难理解吧?我们都知道Spring有IOC控制反转,就是把bean的创建过程交给Spring容器来处理,要用的时候直接跟Spring容器拿就行了。

进阶

上面的示例我们是利用了一个Map来接收数据,但是其实我们更多的都是用实体来接收数据的吧,这就需要我们将数据库查询到的数据和实体类之间创建一个映射关系。

  1. 创建实体类Users
public class Users {
    private int id;
    private String name;
    private int age;
    private String gender;

    public Users() { }

    public Users(int id, String name, int age, String gender) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    @Override
    public String toString() {return super.toString();}
    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;}
    public int getAge() {return age;}
    public void setAge(int age) {this.age = age;}
    public String getGender() {return gender;}
    public void setGender(String gender) {this.gender = gender;}
}
  1. 创建数据库到实体类的映射关系
public class UsersRowMapper implements RowMapper<Users> {
    @Override
    public Users mapRow(ResultSet resultSet, int i) throws SQLException {
        Users u = new Users();
        u.setName(resultSet.getString("name"));
        u.setAge(resultSet.getInt("age"));
        u.setGender(resultSet.getString("gender"));
        return u;
    }
}
  1. 创建业务接口并实现对应的实现类
public interface UserService {
    void addUser(Users user);  // 新增用户
    List<Users> getUsers();  // 获取所有用户
}
public class UserServiceImpl implements UserService {
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(DataSource dataSource){
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public void addUser(Users user) {
        jdbcTemplate.update("INSERT INTO `users`(`name`,`age`,`gender`) VALUES(?,?,?)",
                new Object[]{user.getName(), user.getAge(), user.getGender()},
                new int[]{Types.VARCHAR, Types.INTEGER, Types.VARCHAR});
    }

    @Override
    public List<Users> getUsers() {
        List<Users> res = jdbcTemplate.query("SELECT * FROM `users`", new UsersRowMapper());
        return res;
    }
}
  1. resources目录下新增一个bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd">
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/database_Learn"/>
        <property name="username" value=""/>
        <property name="password" value=""/>
    </bean>
    <!-- 配置业务bean -->
    <bean id="UserService" class="com.sprenedayf.service.impl.UserServiceImpl">
        <!-- 向dataSource注入数据源 -->
        <property name="jdbcTemplate" ref="dataSource"/>
    </bean>
</beans>
  1. 使用
public class Demo3 {
    public static void main(String[] args) {
        // 1. 获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 2. 获取对象
        UserService service = (UserService) ac.getBean("UserService");
        // 3. 执行操作
        List<Users> users = service.getUsers();
        for (Users user : users) {
            System.out.println(user.getName()+" "+user.getAge()+" "+user.getGender());
        }
    }
}

原理

模板方法设计模式与回调机制

在我们看JDBCTemplate源码之前,需要先了解一下模板设计模式。在JDBC中不变的部分是创建连接和销毁连接的部分,改变的部分是中间的sql操作,所以我们希望可以将不变的部分统一管理起来,只需要关注其中要变的部分。模板设计模式就可以很好的解决这个问题。

模板设计模式


模板设计模式是指在一个父类方法中定义一个算法的骨架,将某些步骤推迟到子类中实现,这样可以[让子类在不改变算法整体的结构情况下,重新定义算法中的某些步骤]{.yellow}。
有点抽象,不明白?我们来举个简单的例子立马就清楚了~比如我们现在有个游戏有两个结局,一个是HE一个是BE,女主和我们说的话是一样的,但是根据我们的选择会产生不同的结局,所以这时候我们就可以把通用的剧情用一个类NormalDraft来保存,然后不同的结局就继承这个NormalDraft类来分别实现。

public  abstract class NormalDraft {
    // 常规通用剧情  
    public void templateDraft(){
        // 女主:不可以留下来吗?
        method1();
        // 女主:我知道
        method2();
    }
    protected abstract void method1();
    protected abstract void method2();
}
public class HappyEnding extends NormalDraft {
    @Override
    protected void method2() {
        System.out.println("我想我们可以回家了!");
    }

    @Override
    protected void method1() {
        System.out.println("当然,我所做的这一切就是为了能够留在这里!");
    }
}
public class BadEnding extends NormalDraft {
    @Override
    protected void method2() {
        System.out.println("或许当这个世界上没有小怪兽的时候,我就会回来了");
    }

    @Override
    protected void method1() {
        System.out.println("不行,作为光的化身,我要去拯救全世界!");
    }
}
public class game {
    // 本人唯爱BE,所以当然要走BE线了!!!
    public static void main(String[] args) {
        NormalDraft draft = new BadEnding();
        draft.templateDraft();
    }
}

总结来说就是:[抽象模板类定义了一套工作流程,而具体实现类对工作流程中的某些特定步骤进行了实现]{.yellow}

回调机制


什么是回调?现在有两个类 A 和 B ,A 调用 B,B 反过来又调用 A,这种调用机制就叫作“回调”。在Java中需要使用回调函数的类对象来实现。

public interface ICallback {
    void methodToCallback();
}
public class BClass {
    public void process(ICallback callback) {
        // 前置通用代码
        callback.methodToCallback();  // 相当于是执行了AClass中定义的代码逻辑
        // 后置通用代码
    }
}
public class AClass {
    public static void main(String[] args) {
        BClass b = new BClass();
        b.process(new ICallback() {  // 回调对象
            @Override
            public void methodToCallback() {
                System.out.println("Call back me.");
            }
        });
        // 继续执行AClass的后续代码
    }
}
模板模式 VS 回调
  • 从应用场景上来看,同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。而异步回调跟模板模式有较大差别,更像是观察者模式。
  • 从代码实现上来看,回调和模板模式完全不同。回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。
  • 回调相对于模板模式会更加灵活:
    • 像 Java 这种只支持单继承的语言,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。
    • 回调可以使用匿名类来创建回调对象,可以不用事先定义类;而模板模式针对不同的实现都要定义不同的子类。
    • 如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。

query方法

这里有一个大佬画得时序图我觉得非常直观,可以在后面我说完源码之后回来看一下,应该很好理解:

  1. query方法
@Nullable
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
    Assert.notNull(sql, "SQL must not be null");
    Assert.notNull(rse, "ResultSetExtractor must not be null");
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Executing SQL query [" + sql + "]");
    }

    // 定义的回调类,本质上就是执行查询语句,然后将ResultSet关闭
    class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
        QueryStatementCallback() {}

        @Nullable
        public T doInStatement(Statement stmt) throws SQLException {
            ResultSet rs = null;

            Object var3;
            try {
                rs = stmt.executeQuery(sql);
                var3 = rse.extractData(rs);
            } finally {
                JdbcUtils.closeResultSet(rs);
            }

            return var3;
        }

        public String getSql() {
            return sql;
        }
    }
    // 这里就是根据不同的回调方法来实现不同的操作
    return this.execute((StatementCallback)(new QueryStatementCallback()));
}
  1. execute方法
@Nullable
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
    Assert.notNull(action, "Callback object must not be null");
    // 获取DataSource数据库连接
    Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
    Statement stmt = null;

    Object var11;
    try {
        stmt = con.createStatement();
        this.applyStatementSettings(stmt);
        T result = action.doInStatement(stmt);  // 执行query中定义的QueryStatementCallback的doInStatement代码
        this.handleWarnings(stmt);
        var11 = result;
    } catch (SQLException var9) {
        String sql = getSql(action);
        JdbcUtils.closeStatement(stmt);
        stmt = null;
        DataSourceUtils.releaseConnection(con, this.getDataSource());
        con = null;
        throw this.translateException("StatementCallback", sql, var9);
    } finally {  // 关闭Connection和Statement
        JdbcUtils.closeStatement(stmt);
        DataSourceUtils.releaseConnection(con, this.getDataSource());
    }

    return var11;  // 执行完这一句后返回到query方法中继续执行
}

就这么点核心代码了,是不是很简单?捋一下其实就是,我们的查询操作最终都是要通过query方法来执行,query方法中定义了一个回调类,这个回调类主要操作就是定义ResultSet,执行sql查询,释放ResultSet;我们都知道JDBC操作中ConnectionStatement都是重复的,只有ResultSet是变化的,所以在execute方法中就是来获取和释放ConnectionStatement的;通过使用回调函数,将获取到的statement传入然后执行查询操作,execute方法帮我们完成释放连接的操作,相当于是我们只需要关心sql逻辑即可,是不是方便很多啦?

update方法

  1. update方法
public int update(final String sql) throws DataAccessException {
    Assert.notNull(sql, "SQL must not be null");
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Executing SQL update [" + sql + "]");
    }

    // 回调类
    class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider {
        UpdateStatementCallback() {}

        public Integer doInStatement(Statement stmt) throws SQLException {
            int rows = stmt.executeUpdate(sql);  // 修改了多少条
            if (JdbcTemplate.this.logger.isDebugEnabled()) {
                JdbcTemplate.this.logger.debug("SQL update affected " + rows + " rows");
            }

            return rows;
        }

        public String getSql() {
            return sql;
        }
    }

    return updateCount((Integer)this.execute((StatementCallback)(new UpdateStatementCallback())));
}
  1. execute方法
@Nullable
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
    Assert.notNull(action, "Callback object must not be null");
    // 获取DataSource数据库连接
    Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
    Statement stmt = null;

    Object var11;
    try {
        stmt = con.createStatement();
        this.applyStatementSettings(stmt);
        T result = action.doInStatement(stmt);  // 执行update中定义的UpdateStatementCallback的doInStatement代码
        this.handleWarnings(stmt);
        var11 = result;
    } catch (SQLException var9) {
        String sql = getSql(action);
        JdbcUtils.closeStatement(stmt);
        stmt = null;
        DataSourceUtils.releaseConnection(con, this.getDataSource());
        con = null;
        throw this.translateException("StatementCallback", sql, var9);
    } finally {  // 关闭Connection和Statement
        JdbcUtils.closeStatement(stmt);
        DataSourceUtils.releaseConnection(con, this.getDataSource());
    }

    return var11;  // 执行完这一句后返回到update方法中继续执行
}

发现没有,其实这都是同一段execute代码啊!正式因为传入的callback不同所以才执行了不同的操作,实现了execute代码的复用!妙啊!

连接池

好的,JDBCTemplate帮我们解决了代码复用的问题,但是还有个问题呀,我们频繁的创建和销毁连接真的很没有必要有没有!明明我查询个数据可能只要10ms的时间,但是我创建和销毁连接却可能需要100ms的时间,得不偿失啊!所以这时候就需要我们的连接池了!
:::info no-icon
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。
:::

C3P0

  1. 导入依赖
 <!-- c3p0连接池依赖 -->
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>mchange-commons-java</artifactId>
    <version>0.2.11</version>
</dependency>
  1. 编写配置xml文件
<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd">
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/database_Learn"/>
        <property name="user" value=""/>
        <property name="password" value=""/>
    </bean>
    <!-- 配置JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 向dataSource注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
  1. 测试
public class Demo4 {
    public static void main(String[] args) {
        // 1. 获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("testC3P0.xml");
        // 2. 获取对象
        JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
        // 3. 执行操作
        List<Map<String, Object>> maps = jt.queryForList("SELECT * FROM `users`");
        // 输出
        for (Map<String, Object> map : maps) {
            System.out.println(map.get("name")+" "+map.get("age")+" "+map.get("gender"));
        }
    }
}

可以看到有很多的输出日志,里面这些参数都是可以配置的,可以自己探索一下

Druid

说起连接池感觉更熟悉的还是Druid吧,这里依然是先给个简单的上手示例:

  1. 导入依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.4</version>
</dependency>
  1. 配置xml文件
<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 其实driverClassName可配可不配,可以根据url自动匹配 -->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/database_Learn"/>
        <property name="username" value="root"/>
        <property name="password" value="cljcljclj215"/>

        <!-- 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat、日志用的filter:log4j、防御sql注入的filter:wall -->
        <property name="filters" value="stat" />

        <!-- 最大连接池数量 -->
        <property name="maxActive" value="20" />
        <!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection()时 -->
        <property name="initialSize" value="1" />
        <!-- 获取连接时最大等待时间 -->
        <property name="maxWait" value="6000" />
        <!-- 最小连接池数量 -->
        <property name="minIdle" value="1" />

        <property name="timeBetweenEvictionRunsMillis" value="60000" />
        <property name="minEvictableIdleTimeMillis" value="300000" />

        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <!-- 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭 -->
        <property name="poolPreparedStatements" value="false" />
        <property name="maxOpenPreparedStatements" value="20" />

        <property name="asyncInit" value="true" />
    </bean>
    <!-- 配置JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 向dataSource注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
  1. 测试
public class Demo5 {
    public static void main(String[] args) {
        // 1. 获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("testDruid.xml");
        // 2. 获取对象
        JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
        // 3. 执行操作
        List<Map<String, Object>> maps = jt.queryForList("SELECT * FROM `users`");
        // 输出
        for (Map<String, Object> map : maps) {
            System.out.println(map.get("name")+" "+map.get("age")+" "+map.get("gender"));
        }
    }
}

然后来看看连接池的核心思想是什么吧?

本质上就是图上这个流程,具体的怎么创建和怎么回收的在现阶段暂不做了解,明白现在这些就可以啦~

总结

这篇主要讲了一些稍微基础的数据库操作方法,可能现在项目中更多的都是利用ORM框架,但是在我整理总结这篇文章的时候还是收获了很多东西的,总算是理清了这些数据库操作方法之间的关系和区别,而且对其中的设计思想也清楚了很多~

  • JDBC是我们最原始的操作数据库的方式,每次都需要我们自己创建、销毁连接
  • 连接池是为了解决频繁创建和销毁连接带来的损耗,将数据库连接统一管理起来,其实Java中的各种池子基本上都是干这个的,统一管理~
  • JDBCTemplate是Spring帮我们将JDBC封装了一下,使用了模板设计模式思想,但是在Java中是利用回调函数来实现的,让我们可以把重心放在主要的业务逻辑中,可以与连接池一起使用~

参考

  1. 模板模式 VS Callback回调
  2. DruidDataSource原理
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值