0
点赞
收藏
分享

微信扫一扫

JDBC基础入门(2)

WikongGuan 2022-04-02 阅读 73

其他关于C3P0的详细内容, 可参考C3P0主页.


HikariCP


HikariCP是另一款高性能/”零开销”/高品质的数据库连接池,据测试,其性能优于C3P0(详细信息可参考号称性能最好的JDBC连接池:HikariCP),但国内HikariCP资料不多,其项目主页为https://github.com/brettwooldridge/HikariCP,使用HikariCP需要在pom.xml中添加如下依赖:


<dependency>

    <groupId>com.zaxxer</groupId>

    <artifactId>HikariCP</artifactId>

    <version>2.4.0</version>

</dependency>


HikariCP用方法获取Connection的方法与C3P0大同小异:


public static DataSource getDataSourceHikari(String file) {

    if (dataSource == null) {

        synchronized (ConnectionManger.class) {

            if (dataSource == null) {

                Properties properties = SQLUtil.loadConfig(file);

                HikariConfig config = new HikariConfig();

                config.setDriverClassName(properties.getProperty("mysql.driver.class"));

                config.setJdbcUrl(properties.getProperty("mysql.url"));

                config.setUsername(properties.getProperty("mysql.user"));

                config.setPassword(properties.getProperty("mysql.password"));


                // 设置连接池最大连接数

                config.setMaximumPoolSize(Integer.valueOf(properties.getProperty("pool.max.size")));

                // 设置连接池最少连接数

                config.setMinimumIdle(Integer.valueOf(properties.getProperty("pool.min.size")));

                // 设置最大空闲时间

                config.setIdleTimeout(Integer.valueOf(properties.getProperty("pool.max.idle_time")));

                // 设置连接最长寿命

                config.setMaxLifetime(Integer.valueOf(properties.getProperty("pool.max.life_time")));

                dataSource = new HikariDataSource(config);

            }

        }

    }


    return dataSource;

}


public static Connection getConnectionHikari(String file) {

    return getConnection(getDataSourceHikari(file));

}


附: 

1. ConnectionManger与SQLUtil完整代码地址; 

2. properties文件形式如下:


## Data Source

mysql.driver.class=com.mysql.jdbc.Driver

mysql.url=jdbc:mysql://host:port/database

mysql.user=user

mysql.password=password

pool.max.size=20

pool.min.size=3

pool.init.size=10

pool.max.statements=180

pool.max.idle_time=60

pool.max.life_time=1000


SQL执行


Statement


java.sql.Statement可用于执行DDL/DML/DCL语句:


方法

描述

​boolean execute(String sql)​

Executes the given SQL statement, which may return multiple results.

​ResultSet executeQuery(String sql)​

Executes the given SQL statement, which returns a single ResultSet object.

​int executeUpdate(String sql)​

Executes the given SQL statement, which may be an INSERT, UPDATE, or DELETE statement or an SQL statement that returns nothing, such as an SQL DDL statement.

​int[] executeBatch()​

Submits a batch of commands to the database for execution and if all commands execute successfully, returns an array of update counts.

Java 1.7还新增了closeOnCompletion()方法,当所有依赖于当前Statement的ResultSet关闭时,该Statement自动关闭.


executeUpdate


Statement使用executeUpdate方法执行DDL/DML(不包含select)语句:执行DDL该方法返回0; 执行DML返回受影响的记录数.


DDL

@Test

public void ddlClient() throws SQLException {

    try (

            Connection connection = ConnectionManger.getConnectionHikari("common.properties");

            Statement statement = connection.createStatement()

    ) {

        int res = statement.executeUpdate("CREATE TABLE t_ddl(" +

                "id INT auto_increment PRIMARY KEY, " +

                "username VARCHAR(64) NOT NULL, " +

                "password VARCHAR (36) NOT NULL " +

                ")");

        System.out.println(res);

    }

}


DML

@Test

public void dmlClient() throws SQLException {

    try (

            Connection connection = ConnectionManger.getConnectionHikari("common.properties");

            Statement statement = connection.createStatement()

    ) {

        int res = statement.executeUpdate("INSERT INTO " +

                "t_ddl(username, password) " +

                "SELECT name, password FROM user");

        System.out.println(res);

    }

}


execute


execute方法几乎可以执行任何SQL语句,但较为繁琐(除非不清楚SQL语句类型,否则不要使用execute方法).该方法返回值为boolean,代表执行该SQL语句是否返回ResultSet,然后Statement提供了如下方法来获取SQL执行的结果:


方法

描述

​ResultSet getResultSet()​

Retrieves the current result as a ResultSet object.

​int getUpdateCount()​

Retrieves the current result as an update count; if the result is a ResultSet object or there are no more results, -1 is returned.

SQLUtil

public class SQLUtil {


    // ...


    public static void executeSQL(Statement statement, String sql) {

        try {

            // 如果含有ResultSet

            if (statement.execute(sql)) {

                ResultSet rs = statement.getResultSet();

                ResultSetMetaData meta = rs.getMetaData();

                int columnCount = meta.getColumnCount();

                for (int i = 1; i <= columnCount; ++i) {

                    System.out.printf("%s\t", meta.getColumnName(i));

                }

                System.out.println();


                while (rs.next()) {

                    for (int i = 1; i <= columnCount; ++i) {

                        System.out.printf("%s\t", rs.getObject(i));

                    }

                    System.out.println();

                }

            } else {

                System.out.printf("该SQL语句共影响%d条记录%n", statement.getUpdateCount());

            }

        } catch (SQLException e) {

            throw new RuntimeException(e);

        }

    }

}


client

@Test

public void executeClient() throws SQLException {

    try(

            Connection connection = SQLUtil.getConnection("common.properties");

            Statement statement = connection.createStatement()

            ){

        SQLUtil.executeSQL(statement, "UPDATE t_ddl SET username = 'feiqing'");

        SQLUtil.executeSQL(statement, "SELECT * FROM t_ddl");

    }

}


PreparedStatement


PreparedStatement是Statement的子接口, 它可以预编译SQL语句,编译后的SQL模板被存储在PreparedStatement对象中,每次使用时首先为SQL模板设值,然后执行该语句(因此使用PreparedStatement效率更高). 

创建PreparedStatement需要使用Connection的prepareStatement(String sql)方法,该方法需要传入SQL模板,可以包含占位符参数:


PreparedStatement statement = connection.prepareStatement("INSERT INTO t_ddl(username, password) VALUES (?, ?)")

1

1

PreparedStatement也提供了excute等方法来执行SQL语句, 只是这些方法无须传入参数, 因为SQL语句已经存储在PreparedStatement对象中. 

由于执行SQL前需要为SQL模板传入参数值,PreparedStatement提供了一系列的setXxx(int parameterIndex, X x)方法;另外,如果不清楚SQL模板各参数的类型,可以使用setObject(int parameterIndex, Object x)方法传入参数, 由PreparedStatement来负责类型换.


@Test

public void comparisonPrepared() throws SQLException {

    Connection connection = null;

    try {

        connection = SQLUtil.getConnection("common.properties");

        long start = System.currentTimeMillis();

        try (Statement statement = connection.createStatement()) {

            for (int i = 0; i < 1000; ++i) {

                statement.executeUpdate("INSERT INTO t_ddl(username, password) VALUES ('name" + i + "','password" + i + "')");

            }

        }

        long mid = System.currentTimeMillis();


        try (PreparedStatement statement = connection.prepareStatement("INSERT INTO t_ddl(username, password) VALUES (?, ?)")) {

            for (int i = 0; i < 1000; ++i) {

                statement.setString(1, "name" + i);

                statement.setObject(2, "password" + i);

                statement.execute();

            }

        }

        long end = System.currentTimeMillis();


        System.out.printf("Statement: %d%n", mid - start);

        System.out.printf("Prepared:  %d%n", end - mid);

    } finally {

        try {

            assert connection != null;

            connection.close();

        } catch (SQLException e) {

        }

    }

}


注意: SQL语句的占位符参数只能代替普通值, 不能代替表名/列名等数据库对象, 更不能代替INSERT/SELECT等关键字.

使用PreparedStatement还有另外一个优点:使用PreparedStatement无须拼接SQL字符串,因此可以防止SQL注入(关于SQL注入的问题可参考SQL Injection, 现代的ORM框架都解决了该问题).


注: 

1. 默认使用PreparedStatement是没有开启预编译功能的,需要在URL中给出useServerPrepStmts=true参数来开启此功能; 

2. 当使用不同的PreparedStatement对象来执行相同SQL语句时,还是会出现编译两次的现象,这是因为驱动没有缓存编译后的函数key,导致二次编译.如果希望缓存编译后的函数key,那么就要设置cachePrepStmts=true参数. 

3. 另外, 还可以设置预编译缓存的大小:cachePrepStmts=true&prepStmtCacheSize=50&prepStmtCacheSqlLimit=300` 

jdbc:mysql://host:port/database?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=50&prepStmtCacheSqlLimit=300

CallableStatement


在数据库中创建一个简单的存储过程add_pro:


mysql> delimiter //

mysql> CREATE PROCEDURE add_pro(a INT, b INT, OUT sum INT)

    -> BEGIN

    -> SET sum = a + b;

    -> END

    -> //

mysql> delimiter ;


delimiter //会将SQL语句的结束符改为//, 这样就可以在创建存储过程时使用;作为分隔符. MySQL默认使用;作为SQL结束符.

调用存储过程需要使用CallableStatement,可以通过Connection的prepareCall()方法来创建,创建时需要传入调用存储过程的SQL语句,形式为:


{CALL procedure_name(?, ?, ?)}


存储过程的参数既有入参,也有回参; 入参可通过setXxx(int parameterIndex/String parameterName, X x)方法传入;回参可以通过调用registerOutParameter(int parameterIndex, int sqlType)来注册, 经过上面步骤, 就可以调用execute()方法来调用该存储过程, 执行结束, 则可通过getXxx(int parameterIndex/String parameterName)方法来获取指定回参的值:


@Test

public void callableClient() throws SQLException {

    try (

            Connection connection = SQLUtil.getConnection("common.properties");

            CallableStatement statement = connection.prepareCall("{CALL add_pro(?, ?, ?)}")

    ) {

        // statement.setInt("a", 1);

        statement.setInt(1, 11);

        // statement.setInt("b", 2);

        statement.setInt(2, 22);


        // 注册CallableStatement回参

        statement.registerOutParameter(3, Types.INTEGER);

        // 执行存储过程

        statement.execute();

        // statement.getInt(3);

        System.out.printf("存储过程执行结果为: %d%n", statement.getInt("sum"));

    }

}


举报

相关推荐

0 条评论