第⑩三章

JDBC的出众用法:

初稿出处:
翡青

  JDBC4.2常用接口和类简介:

JDBC(Java Database
Connectivity)代表Java编程语言与数据库连接的正统API,不过JDBC只是接口,JDBC驱动才是真的的接口达成,没有驱动不可能完毕数据库连接.
每种数据库厂商都有谈得来的驱动,用来一而再自身集团的数据库(如Oricle, MySQL,
DB2, MS SQLServer).

    DriverManager:用于管理JDBC驱动的服务类,程序中央银行使该类的机要意义是获得Connection对象,该类包括如下方法:

图片 1

      public static synchronized Connection getConnection(String
url, String user, String  pass) throws
SQLException:该措施赢得url对应数据库的连日

下边大家以MySQL为例,JDBC编制程序大概步骤如下:

    Connection:代表数据库连接对象,各类Connection代表二个大体连接会话。想要访问数据库,必须先取得数据库连接,该接口的常用方法如下:

/**
 * @author jifang
 * @since 16/2/18 上午9:02.
 */
public class SQLClient {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
 /* 加载数据库驱动 */
 Class.forName("com.mysql.jdbc.Driver");
/* 通过 DriverManager 获取数据库连接 */
 Connection connection = DriverManager.getConnection("jdbc:mysql://host:port/database", "user", "password");

 /* 通过 Connection 创建 Statement */
 Statement statement = connection.createStatement();

 /* 通过 Statement 执行SQL */
 ResultSet users = statement.executeQuery("SELECT * FROM user");

 /* 操作 ResultSet 结果集 */
 int columnCount = users.getMetaData().getColumnCount();
 while (users.next()) {
 for (int i = 1; i <= columnCount; ++i) {
 System.out.printf("%s\t", users.getObject(i));
 }
 System.out.println();
 }
/* 回收数据库资源(推荐使用Java1.7提供的 可以自动关闭资源的try) */
 users.close();
 statement.close();
 connection.close();
 }
}

      1.Statement createStatement() throws
SQLException:该方法再次来到2个Statement对象。

  • 在意: 供给在pom.xml中添加如下MySQL驱动:

      2.PreparedStatement prepareStatement(String sql) throws
SQLException:该措施重临预编写翻译的Statement对象,即将SQL语句提交到数据库实行预编写翻译。

      3.CallableStatement prepareCall(String sql) throws
SQLException:该格局再次回到CallableStatement对象,该对象用于调用存款和储蓄进度

<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>5.1.36</version>
</dependency>

    上边五个法子都回去用于实施SQL语句的Statement对象,PreparedStatement、CallableStatement是Statement的子类,唯有获得了Statement之后才可实施SQL语句。

注: ResultSet参数columnIndex索引从1开始,而不是0!

    除此之外,Connection还有如下多少个用于控制作业的情势:

ConnectionManger

      1.Savepoint setSavepoint():创设多少个保存点

DriverManger

      2.Savepoint setSavepoint(String
name):以钦赐名字创办2个保存点

JDBC规定: 驱动类在被加载时,要求积极把自个儿注册到DriverManger中:

      3.void setTransactionIsolation(int
level):设置工作的隔开级别

  • com.mysql.jdbc.Driver

      4.void rollback():回滚事务

      5.void rollback(Savepoint
savepoint):将工作回滚到钦赐的保存点

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
 //
 // Register ourselves with the DriverManager
 //
 static {
 try {
 java.sql.DriverManager.registerDriver(new Driver());
 } catch (SQLException E) {
 throw new RuntimeException("Can't register driver!");
 }
 }
/**
 * Construct a new driver and register it with DriverManager
 * 
 * @throws SQLException
 * if a database error occurs.
 */
 public Driver() throws SQLException {
 // Required for Class.forName().newInstance()
 }
}

      6.void setAutoCommit(boolean
autoCommit):关闭自动提交、打开工作

代码展现:只要去加载com.mysql.jdbc.Driver类那么就会实施static块,
从而把com.mysql.jdbc.Driver注册到DriverManager中.

      7.void commit():提交业务

java.sql.DriverManager是用于管理JDBC驱动的服务类,其首要功效是得到Connection对象:

    Java7 为Connection新增了

    1. static Connection getConnection(String url, Properties info)
    1. static Connection getConnection(String url, String user, String
      password)

      setSchema(String
schema)、getSchema()多少个章程:用于控制该Connection访问的数据库Schema

另: 还足以在赢得Connection的U景逸SUVL中安装参数,如:
jdbc:mysql://host:port/database?useUnicode=true&characterEncoding=UTF8
useUnicode=true&characterEncoding=UTF8钦命连接数据库的过程中应用Unicode字符集/UTF-8编码;

      setNetworkTimeout(Executor executor, int
milliseconds)、getNetwork提姆eout()四个方式:用于控制数据库连接超时行为。

Connection

    Statement:用于实践SQL语句的工具接口,常用方法如下:

java.sql.Connection代表数据库连接,各样Connection代表一个大体连接会话,
该接口提供如下创造Statement的格局, 唯有获得Statement之后才可实施SQL语句:

      1.ResultSet executeQuery(String sql) throws
SQLException:该格局用于实践查询语句,并回到查询结果对应的ResultSet对象。该办法只好用来实施查询语句

方法 描述
Statement createStatement() Creates a Statement object for sending SQL statements to the database.
PreparedStatement prepareStatement(String sql) Creates a PreparedStatement object for sending parameterized SQL statements to the database.
CallableStatement prepareCall(String sql) Creates a CallableStatement object for calling database stored procedures.

      2.int executeUpdate(String sql) throws
SQLException:该措施用于执行DML(数据操作语言)语句,并再次来到受影响的行数;该方法也可用来实践DDL(数据定义

中间Connection还提供了如下决定工作/保存点的措施:

       语言)语句执行DDL语句将重返0

方法 描述
Savepoint setSavepoint(String name) Creates a savepoint with the given name in the current transaction and returns the new Savepoint object that represents it.
void setTransactionIsolation(int level) Attempts to change the transaction isolation level(事务隔离级别) for this Connection object to the one given.
void setAutoCommit(boolean autoCommit) Sets this connection’s auto-commit mode to the given state.
void rollback() Undoes all changes made in the current transaction and releases any database locks currently held by this Connection object.
void rollback(Savepoint savepoint) Undoes all changes made after the given Savepoint object was set.
void commit() Makes all changes made since the previous commit/rollback permanent and releases any database locks currently held by this Connection object.

      3.boolean execute(String sql) throws
SQLException:该格局可实施别的SQL语句。若实行后首先个结实为ResultSet对象,则赶回true;若执行后第①个结果为受影

以上措施还存在区别的重载格局, 详细可参照JDK文书档案.

       响的行数或从不任何结果,则赶回false。

ConnectionManger

    Java7为Statement新增了closeOnCompletion()方法:若Statement执行了该措施,则当全数正视于该Statement的ResultSet关闭时,该Statement会自动关闭。

是因为获得Connection的手续单一,每回大概只是加载的参数差异,由此大家得以将获得Connection的操作封装成1个办法,并使其从配置文件中加载配置:

    Java7还为Statement提供了isCloseOnCompletion()方法:用于判断该Statement是或不是打开了“closeOnCompletion”.

  • 配置文件格局

    Java8为Statement新增了四个重载的executeLargeUpdate()方法:那个办法相当于增强版的executeUpdate()方法,重临值类型为long,即当DML语句影响的记录条数超过

     Integer.MAX_VALUE时,就活该使用executeLargeUpdate()方法。

## Data Source
mysql.driver.class=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://host:port/database
mysql.user=admin
mysql.password=admin

    PreparedStatement:预编译的Statement对象。PreparedStatement是Statement的子接口,它同意数据库预编译SQL语句(这个SQL语句普通带有参数),今后每一趟只

  • ConnectionManger

     改变SQL命令的参数,防止数据库每一趟都亟需编译SQL语句,因而品质更好。相对于Statement而言,使用PreparedStatement执行SQL语句时,无需再盛传SQL语句,只

     要为预编写翻译的SQL语句流传参数数值即可。所以它比Statement多了之类方法:

/**
 * @author jifang
 * @since 16/2/19 上午10:40.
 */
public class ConnectionManger {
/*获取原生Connection*/
public static Connection getConnection(String file) {
 Properties config = SQLUtil.loadConfig(file);
 try {
 Class.forName(config.getProperty("mysql.driver.class"));
 String url = config.getProperty("mysql.url");
 String username = config.getProperty("mysql.user");
 String password = config.getProperty("mysql.password");
 return DriverManager.getConnection(url, username, password);
 } catch (SQLException | ClassNotFoundException e) {
 throw new RuntimeException(e);
 }
 }
}

      1.void setXxx(int parameterIndex, Xxx
value):该办法依照传入参数值的品种不一样,供给使用差别的方法。传入的值依据目录传给SQL语句中内定地方的参数。

  • SQLUtil

  ResultSet:结果集对象。该对象涵盖访问查询结果的章程,ResultSet能够透过列索引或列名获得列数据。它含有了之类常用方法来运动记录指针:

    1.void close():释放ResultSet对象。

/**
 * @author jifang
 * @since 16/2/18 上午8:24.
 */
public class SQLUtil {
/**
 * 加载.properties配置文件
 *
 * @param file
 * @return
 */
 public static Properties loadConfig(String file) {
Properties properties = new Properties();
 try {
 properties.load(ClassLoader.getSystemResourceAsStream(file));
 return properties;
 } catch (IOException e) {
 throw new RuntimeException(e);
 }
 }
}

    2.boolean absolute(int
row):将结果集的记录指针移动到第row行,若row是负数,则运动到倒数第row行。若移动后的记录指针指向一条有效记录,则该形式重返true

数据库连接池

    3.void
beforeFirst():将ResultSet的记录指针定位到首行此前,这是ResultSet结果集记录指针的上马状态——记录指针的苗子地方放在第2行在此之前

近来通过DriverManger获得Connection,
贰个Connection对应三个实际的情理连接,每便操作都亟需开拓物理连接,
使用完后即时关闭;那样翻来覆去的打开/关闭连接会造成不供给的数据库系统品质消耗.

    4.boolean
first():将ResultSet的记录指针定位到首行。若移动后的笔录指针指向一条有效记录,则该措施再次来到true。

数据库连接池提供的化解方案是:当使用运维时,主动建立充足的数据库连接,并将这几个连接协会成连接池,每一次请求连接时,无须重新打开连接,而是从池中取出已有连接,使用完后并不实际关闭连接,而是归还给池.

    5.boolean
previous():将ResultSet的记录指针定位到上一行。若移动后的笔录指针指向一条有效记录,则该措施重返true。

JDBC数据库连接池使用javax.sql.DataSource表示, DataSource只是1个接口,
其落到实处平常由服务器提供商(如WebLogic,
WebShere)或开源组织(如DBCP,C3P0和HikariCP)提供.

    6.boolean
next():将ResultSet的笔录指针定位到下一行,若移动后的记录指针指向一条有效记录,则该情势再次来到true。

  • 数据库连接池的常用参数如下:

    7.boolean
last():将ResultSet的笔录指针定位到最后一行,若移动后的笔录指针指向一条有效记录,则该方法重回true。 

  1. 数据库初阶连接数;
  2. 连接池最阿比让接数;
  3. 连接池最小连接数;
  4. 连接池每一趟扩展的容积;

    8.void afterLast():将ResultSet的笔录指针定位到终极一行之后。

C3P0

JDBC编制程序步骤:

汤姆cat暗许使用的是DBCP连接池,但相比较,C3P0则比DBCP更胜一筹(Hibernate推荐使用C3P0),C3P0不仅能够活动清理不再采用的Connection,
还是能自动清理Statement/ResultSet, 使用C3P0需求在pom.xml中添加如下依赖:

  1.加载数据库驱动:

<dependency>
 <groupId>com.mchange</groupId>
 <artifactId>c3p0</artifactId>
 <version>0.9.5.2</version>
</dependency>
<dependency>
 <groupId>com.mchange</groupId>
 <artifactId>mchange-commons-java</artifactId>
 <version>0.2.11</version>
</dependency>

    常常使用Class类的forName()静态方法来加载驱动:

  • ConnectionManger

      Class.forName(driverClass);//driverClass就是数据库驱动类所对应的字符串。如:加载MySQL的驱动代码

      Class.forName(“com.mysql.jdbc.Driver”);

public class ConnectionManger {
/*双重检测锁保证DataSource单例*/
 private static DataSource dataSource;
/*获取DataSource*/
public static DataSource getDataSourceC3P0(String file) {
 if (dataSource == null) {
 synchronized (ConnectionManger.class) {
 if (dataSource == null) {
 Properties config = SQLUtil.loadConfig(file);
 try {
 ComboPooledDataSource source = new ComboPooledDataSource();
 source.setDriverClass(config.getProperty("mysql.driver.class"));
 source.setJdbcUrl(config.getProperty("mysql.url"));
 source.setUser(config.getProperty("mysql.user"));
 source.setPassword(config.getProperty("mysql.password"));
// 设置连接池最大连接数
 source.setMaxPoolSize(Integer.valueOf(config.getProperty("pool.max.size")));
 // 设置连接池最小连接数
 source.setMinPoolSize(Integer.valueOf(config.getProperty("pool.min.size")));
 // 设置连接池初始连接数
 source.setInitialPoolSize(Integer.valueOf(config.getProperty("pool.init.size")));
 // 设置连接每次增量
 source.setAcquireIncrement(Integer.valueOf(config.getProperty("pool.acquire.increment")));
 // 设置连接池的缓存Statement的最大数
 source.setMaxStatements(Integer.valueOf(config.getProperty("pool.max.statements")));
 // 设置最大空闲时间
 source.setMaxIdleTime(Integer.valueOf(config.getProperty("pool.max.idle_time")));
dataSource = source;
 } catch (PropertyVetoException e) {
 throw new RuntimeException(e);
 }
 }
 }
 }
 return dataSource;
 }
/*获取Connection*/
public static Connection getConnectionC3P0(String file) {
 return getConnection(getDataSourceC3P0(file));
 }
public static Connection getConnection(DataSource dataSource) {
 try {
 return dataSource.getConnection();
 } catch (SQLException e) {
 throw new RuntimeException(e);
 }
 }
// ...
}

  2.透过DriverManager获取数据库连接:

C3P0还能利用铺排文件来开端化连接池(配置文件可以是properties/XML,
在此仅介绍XML),C3P0配置文件名必须为c3p0-config.xml,其放在类路径下:

    //获取数据库连接

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
 <default-config>
 <property name="jdbcUrl">jdbc:mysql://host:port/database</property>
 <property name="driverClass">com.mysql.jdbc.Driver</property>
 <property name="user">user</property>
 <property name="password">password</property>
 <property name="acquireIncrement">5</property>
 <property name="initialPoolSize">10</property>
 <property name="minPoolSize">3</property>
 <property name="maxPoolSize">20</property>
 </default-config>
 <named-config name="mysql-config">
 <property name="jdbcUrl">jdbc:mysql://host:port/common</property>
 <property name="driverClass">com.mysql.jdbc.Driver</property>
 <property name="user">user</property>
 <property name="password">password</property>
 <property name="acquireIncrement">5</property>
 <property name="initialPoolSize">10</property>
 <property name="minPoolSize">3</property>
 <property name="maxPoolSize">20</property>
 </named-config>
</c3p0-config>

    DriverManager.getConnection(String url, String user, String
pass);//数据库UHighlanderL、登录数据库的用户名和密码。

如此那般, 我们在创立ComboPooledDataSource时就默许加载配置文件中的配置,
无须手动配置:

    数据库ULacrosseL平时遵从如下写法:

public static DataSource getDataSourceC3P0(String file) {
 if (dataSource == null) {
 synchronized (ConnectionManger.class) {
 if (dataSource == null) {
 dataSource = new ComboPooledDataSource();
 }
 }
 }
 return dataSource;
}

    jdbc:subprotocol:other stuff

C3P0配置文件能够布置多个三番五次新闻, 并为各样配置命名,
那样能够便宜的经过配备名称来切换配置信息:

    jdbc是定点的写法,subprotocol钦命连接到一定数据库的驱动,other
stuff也不是固定的(由数量库定)

public static DataSource getDataSourceC3P0(String file) {
 if (dataSource == null) {
 synchronized (ConnectionManger.class) {
 if (dataSource == null) {
 dataSource = new ComboPooledDataSource("mysql-config");
 }
 }
 }
 return dataSource;
}

    MySQL数据库的URAV4L写法如下:

别的有关C3P0的详尽内容, 可参考C3P0主页.

    jdbc:mysql://hostname:port/databasename

HikariCP

  3.经过Connection对象创设Statement对象。有如下多少个措施:

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

    1.createStatement():创设基本的Statement对象

<dependency>
 <groupId>com.zaxxer</groupId>
 <artifactId>HikariCP</artifactId>
 <version>2.4.0</version>
</dependency>

    2.prepareStatement(String
sql):依据传入的SQL语句成立预编写翻译的Statement对象

HikariCP用方法取得Connection的形式与C3P0清远小异:

    3.prepateCall(String
sql):根据传入的SQL语句创制CllableStatement对象

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));
}

  4.施用Statement执行SQL语句。有如下几个措施:

附:

    1.execute():可实施此外SQL语句,但相比较麻烦

  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

    2.executeUpdate():主要用以执行DML和DDL语句。执行DML语句重回受SQL语句影响的行数,执行DDL语句重回0

SQL执行

    3.executeQuery():只可以进行查询语句,执行后回来代表询问结果的ResultSet对象

Statement

  5.操作结果集:

java.sql.Statement可用来实施DDL/DML/DCL语句:

    若执行SQL语句是查询语句,则实行结果将回来一个ResultSet对象,该指标里保存了SQL语句询问的结果。程序可因此操作该ResultSet对象来取出查询结果:

方法 描述
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.

      1.next()、previous()、first()、last()、beforeFirst()、afterLast()、absolute()等移动记录指针的方法

Java
1.7还新增了closeOnCompletion()方法,当全部依赖于近来Statement的ResultSet关闭时,该Statement自动关闭.

      2.getXxx()方法取得记录指针指向行、特定列的值。该办法既可使用列索引作为参数,也可应用列名作为参数。使用列索引作为参数品质更好,使用列名作为参数可

executeUpdate

       读性更好

Statement使用executeUpdate方法执行DDL/DML(不含有select)语句:执行DDL该措施再次来到0;
执行DML再次回到受影响的记录数.

  6.回收数据库财富:

  • DDL

    包罗关闭ResultSet、Statement和Connection等财富。

  上面程序简单示范了JDBC编制程序,并通过ResultSet获得结果集的进程:

@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);
 }
}

图片 2图片 3

  • DML
 1 drop database if exists select_test;
 2 create database select_test;
 3 use select_test;
 4 # 为了保证从表参照的主表存在,通常应该先建主表。
 5 create table teacher_table
 6 (
 7     # auto_increment:实际上代表所有数据库的自动编号策略,通常用作数据表的逻辑主键。
 8     teacher_id int auto_increment,
 9     teacher_name varchar(255),
10     primary key(teacher_id)
11 );
12 create table student_table
13 (
14     # 为本表建立主键约束
15     student_id int auto_increment primary key,
16     student_name varchar(255),
17     # 指定java_teacher参照到teacher_table的teacher_id列
18     java_teacher int,
19     foreign key(java_teacher) references teacher_table(teacher_id)
20 );
21 insert into teacher_table
22 values
23 (null , 'Yeeku');
24 insert into teacher_table
25 values
26 (null , 'Leegang');
27 insert into teacher_table
28 values
29 (null , 'Martine');
30 insert into student_table
31 values
32 (null , '张三' , 1);
33 insert into student_table
34 values
35 (null , '张三' , 1);
36 insert into student_table
37 values
38 (null , '李四' , 1);
39 insert into student_table
40 values
41 (null , '王五' , 2);
42 insert into student_table
43 values
44 (null , '_王五' , 2);
45 
46 insert into student_table
47 values
48 (null , null , 2);
49 insert into student_table
50 values
51 (null , '赵六' , null);

数据库建表语句

@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);
 }
}

图片 4

  • execute

将使得(mysql-connector-java-5.1.30-bin.jar)放到java目录的下的jre/lib/ext/目录下边。或然将使得的不二法门添加到classpath环境变量前边。

execute方法大致能够执行别的SQL语句,但相比较麻烦(除非不知底SQL语句类型,不然不要使用execute方法).该方法重返值为boolean,代表执行该SQL语句是或不是再次来到ResultSet,然后Statement提供了如下方法来获取SQL执行的结果:

图片 5

方法 描述
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.

图片 6图片 7

 

 1 import java.sql.Connection;
 2 import java.sql.DriverManager;
 3 import java.sql.Statement;
 4 import java.sql.ResultSet;
 5 
 6 public class ConnMySql{
 7     public static void main(String[] args) throws Exception{
 8         //1.加载驱动,使用反射知识,现在记住这么写
 9         Class.forName("com.mysql.jdbc.Driver");
10         try(
11             //2.使用DriverManager获取数据库连接
12             //其中返回的Connection就代表了Java程序和数据库的连接
13             //不同数据库的URL写法需要查询驱动文档,用户名、密码由DBA分配
14             Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/select_test", "root", "123456");
15             //3.使用Connection来创建一个Statement对象
16             Statement stmt = conn.createStatement();
17             //4.执行SQL语句
18             /*
19             Statement有三种执行SQL语句的方法:
20                 1.execute()可执行任何SQL语句-返回一个boolean值,若执行后第一个结果是ResultSet,则返回true,否则返回false
21                 2.executeQuery()执行select语句-返回查询到的结果集
22                 3.executeUpdate()用于执行DML语句-返回一个整数,代表被SQL语句影响的记录条数
23             */
24             ResultSet rs = stmt.executeQuery("select s.*, teacher_name"
25                 + " from student_table s , teacher_table t"
26                 + " where t.teacher_id = s.java_teacher")){
27                     //ResultSet有一系列的getXxx(列索引 | 列名)方法,用于获取记录指针
28                     //指向行、特定列的值,不断地使用next()将记录指针下移一行
29                     //若移动之后记录指针依然指向有效行,则next()方法返回true
30                     while(rs.next()){
31                         System.out.println(rs.getInt(1) + "\t"
32                             + rs.getString(2) + "\t"
33                             + rs.getString(3) + "\t"
34                             + rs.getString(4));
35                     }
36                 }
37     }
38 }
  • SQLUtil

View Code

图片 8

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);
 }
 }
}

 执行SQL语句的艺术:

  • client

  使用Java8新增的executeLargeUpdate()方法执行DDL和DML语句:

  使用statement执行DDL和DML语句的手续与实践平常查询语句的手续基本相似,差别在于执行了DDL语句后再次回到值为0,执行了DML语句后再次回到值为备受震慑的记录条数。

@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");
 }
}

  MySQL暂不援救executeLargeUpdate()方法。所以我们使用executeUpdate()方法。

PreparedStatement

  上面包车型地铁先后并不曾一向把数据库连接消息写在代码中,而是使用二个mysql.ini文件(正是properties文件)来保存数据库连接音信,那是相比较成熟的做法——当须求把应用程

PreparedStatement是Statement的子接口,
它能够预编译SQL语句,编写翻译后的SQL模板被积存在PreparedStatement对象中,每一遍使用时首先为SQL模板设值,然后实施该语句(因而利用PreparedStatement功能更高).
开创PreparedStatement要求运用Connection的prepareStatement(String
sql)方法,该格局必要传入SQL模板,能够分包占位符参数:

   序从成本环境移植到生产条件时,无需修改源代码,只供给修改mysql.ini配置文件即可:

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

 mysql.ini文件内容:

PreparedStatement也提供了excute等办法来推行SQL语句,
只是那些点子毫无传入参数, 因为SQL语句已经储存在PreparedStatement对象中.

图片 9

是因为实施SQL前要求为SQL模板传入参数值,PreparedStatement提供了一文山会海的setXxx(int
parameterIndex, X
x)方法;其余,若是不掌握SQL模板各参数的品类,可以选用setObject(int
parameterIndex, Object x)方法传入参数,
由PreparedStatement来负责项目转换.

mysql.ini和ExecuteDDL.java文件所放位置:

@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) {
 }
 }
}

图片 10

在意: SQL语句的占位符参数只好代替普通值, 不可能代替表名/列名等数据库对象,
更无法代表INSE福睿斯T/SELECT等首要字.
应用PreparedStatement还有别的1个亮点:使用PreparedStatement无须拼接SQL字符串,因而能够制止SQL注入(关于SQL注入的标题可参照SQL
Injection, 现代的O奥迪Q5M框架都消除了该难点).

图片 11图片 12

注:
1.
默许使用PreparedStatement是没有拉开预编写翻译作用的,需求在U福特ExplorerL中付出useServerPrepStmts=true参数来开启此作用;
2.
当使用不一致的PreparedStatement对象来推行同样SQL语句时,依旧会现出编写翻译两回的景观,那是因为驱动没有缓存编译后的函数key,导致三回编写翻译.借使愿意缓存编写翻译后的函数key,那么快要设置cachePrepStmts=true参数.

 1 import java.io.FileInputStream;
 2 import java.util.Properties;
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.Statement;
 6 
 7 
 8 public class ExecuteDDL{
 9     private String driver;
10     private String url;
11     private String user;
12     private String pass;
13     public void initParam(String paramFile) throws Exception{
14         //使用Properties类来加载属性文件
15         Properties props = new Properties();
16         props.load(new FileInputStream(paramFile));
17         driver = props.getProperty("driver");
18         url = props.getProperty("url");
19         user = props.getProperty("user");
20         pass = props.getProperty("pass");
21     }
22     
23     public void createTable(String sql) throws Exception{
24         //加载驱动
25         Class.forName(driver);
26         try(
27             //获取数据库连接
28             Connection conn = DriverManager.getConnection(url, user, pass);
29             //使用Connection来创建一个Statement对象
30             Statement stmt = conn.createStatement()){
31                 //执行DDL语句,创建数据表
32                 stmt.executeUpdate(sql);
33             }
34     }
35     
36     public static void main(String[] args) throws Exception{
37         ExecuteDDL ed = new ExecuteDDL();
38         ed.initParam("mysql.ini");
39         ed.createTable("create table jdbc_test "
40             + "( jdbc_id int auto_increment primary key, "
41             + "jdbc_name varchar(255), "
42             + "jdbc_desc text);");
43             System.out.println("------建表成功------");
44     }
45 }
  1. 别的,
    还能安装预编写翻译缓存的轻重缓急:cachePrepStmts=true&prepStmtCacheSize=50&prepStmtCacheSqlLimit=300`
    jdbc:mysql://host:port/database?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=50&prepStmtCacheSqlLimit=300

View Code

CallableStatement

图片 13

在数据库中成立1个简练的存款和储蓄进度add_pro:

  使用executeUpdate()方法执行DML语句:

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语句,情势为:

图片 14图片 15

{CALL procedure_name(?, ?, ?)}
 1 import java.io.FileInputStream;
 2 import java.util.Properties;
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.Statement;
 6 
 7 public class ExecuteDML{
 8     private String driver;
 9     private String url;
10     private String user;
11     private String pass;
12     
13     public void initParam(String paramFile) throws Exception{
14         Properties props = new Properties();
15         props.load(new FileInputStream(paramFile));
16         driver = props.getProperty("driver");
17         url = props.getProperty("url");
18         user = props.getProperty("user");
19         pass = props.getProperty("pass");
20     }
21     
22     public int insertData(String sql) throws Exception{
23         //加载驱动
24         Class.forName(driver);
25         try(
26             //获取数据库连接
27             Connection conn = DriverManager.getConnection(url, user, pass);
28             //使用Connection来创建一个Statement对象
29             Statement stmt = conn.createStatement()){
30                 //执行SQL语句,返回受影响的记录条数
31                 return stmt.executeUpdate(sql);
32             }
33     }
34     
35     public static void main(String[] args) throws Exception{
36         ExecuteDML ed = new ExecuteDML();
37         ed.initParam("mysql.ini");
38         int result = ed.insertData("insert into jdbc_test(jdbc_name,jdbc_desc)"
39             + "select s.student_name , t.teacher_name "
40             + "from student_table s , teacher_table t "
41             + "where s.java_teacher = t.teacher_id;");
42         System.out.println("------系统中一共有" + result + "条记录受影响------");
43     }
44 }

积存进度的参数既有入参,也有回参; 入参可通过setXxx(int
parameterIndex/String parameterName, X
x)方法传入;回参能够因此调用registerOutParameter(int parameterIndex, int
sqlType)来注册, 经过地点步骤, 就足以调用execute()方法来调用该存储进度,
执行完成, 则可透过getXxx(int parameterIndex/String
parameterName)方法来收获钦定回参的值:

View Code

@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"));
 }
}

图片 16

操作结果集

图片 17

JDBC使用ResultSet封装查询结果,然后经过ResultSet的笔录指针来读取/更新记录.并提供了ResultSetMetaDate来收获ResultSet对象的元数据音信.

  使用execute()方法执行SQL语句:

ResultSet

    Statement的execute()方法差不多能够推行别的SQL语句,但它执行SQL语句相比较费心,平时没有须求使用execute()方法来实施SQL语句。

java.sql.ResultSet是结果集对象,能够通过列索引/列名来读/写多少,
它提供了之类常用方法来运动记录指针:

    使用execute()方法执行SQL语句的再次回到值只是boolean值,它标志执行该SQL语句是或不是重临了ResultSet对象,Statement提供了之类多个措施来取得执行结果:

方法 描述
boolean next() Moves the cursor froward one row from its current position.
boolean previous() Moves the cursor to the previous row in this ResultSet object.
boolean first() Moves the cursor to the first row in this ResultSet object.
boolean last() Moves the cursor to the last row in this ResultSet object.
void beforeFirst() Moves the cursor to the front of this ResultSet object, just before the first row.
void afterLast() Moves the cursor to the end of this ResultSet object, just after the last row.
boolean absolute(int row) Moves the cursor to the given row number in this ResultSet object.
boolean relative(int rows) Moves the cursor a relative number of rows, either positive or negative.

      1.getResultSet():获取该Statement执行查询语句所重回的ResultSet对象

当把记录指针定位到钦赐行后, ResultSet可通过getXxx(int columnIndex/String
columnLabel)方法来得到钦命项目值.或采取<T> T getObject(int
columnIndex/String columnLabel, Class<T> type)来获取任意档次值.

      2.getUpdateCount():获取该Statement执行DML语句所影响的笔录行数。

可更新/滚动的ResultSet

    上面程序示范了运用Statement的execute()方法来进行任意的SQL语句:

以默许方式打开的ResultSet是不行更新的,获得可更新的ResultSet,要求在开立Statement/PreparedStatement时传出如下五个参数:

图片 18图片 19

  • resultSetType: 控制ResultSet可活动方向
 1 import java.util.Properties;
 2 import java.io.FileInputStream;
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.Statement;
 6 import java.sql.ResultSet;
 7 import java.sql.ResultSetMetaData;
 8 
 9 public class ExecuteSQL{
10     private String driver;
11     private String url;
12     private String user;
13     private String pass;
14     
15     public void initParam(String paramFile) throws Exception{
16         //使用Properties类来加载属性文件
17         Properties props = new Properties();
18         props.load(new FileInputStream(paramFile));
19         driver = props.getProperty("driver");
20         url = props.getProperty("url");
21         user = props.getProperty("user");
22         pass = props.getProperty("pass");
23     }
24     
25     public void executeSql(String sql) throws Exception{
26         //加载数据库驱动
27         Class.forName(driver);
28         try(
29             //获取数据库连接
30             Connection conn = DriverManager.getConnection(url, user, pass);
31             //通过Connection创建一个Statement
32             Statement stmt = conn.createStatement()){
33                 //执行SQL语句,返回boolean值表示是否包含ResultSet
34                 boolean hasResultSet = stmt.execute(sql);
35                 //若执行后有ResultSet结果集
36                 if(hasResultSet){
37                     try(
38                         //获取结果集
39                         ResultSet rs = stmt.getResultSet()){
40                             //ResultSetMetaData是用于分析结果集的元数据接口
41                             ResultSetMetaData rsmd = rs.getMetaData();
42                             int columnCount = rsmd.getColumnCount();
43                             //迭代输出ResultSet对象
44                             while(rs.next()){
45                                 //依次输出每列的值
46                                 for(int i = 0; i < columnCount; i++){
47                                     System.out.print(rs.getString(i + 1) + "\t");
48                                 }
49                                 System.out.print("\n");
50                             }
51                         }
52                 }else{
53                     System.out.println("该SQL语句影响的记录有" + stmt.getUpdateCount() + "条");
54                 }
55             }
56     }
57     
58     public static void main(String[] args) throws Exception{
59         ExecuteSQL es = new ExecuteSQL();
60         es.initParam("mysql.ini");
61         System.out.println("------执行删除表的DDL语句------");
62         es.executeSql("drop table if exists my_test");
63         System.out.print("------执行建表的DDL语句------");
64         es.executeSql("create table my_test"
65             + "(test_id int auto_increment primary key, "
66             + "test_name varchar(255))");
67         System.out.println("------执行插入数据的DML语句------");
68         es.executeSql("insert into my_test(test_name) "
69             + "select student_name from student_table");
70         System.out.println("------执行查询数据的查询语句------");
71         es.executeSql("select * from my_test");
72     }
73 }
ResultSet.TYPE_FORWARD_ONLY The constant indicating the type for a ResultSet object whose cursor may move only forward.
ResultSet.TYPE_SCROLL_INSENSITIVE The constant indicating the type for a ResultSet object that is scrollable but generally not sensitive to changes to the data that underlies the ResultSet.
ResultSet.TYPE_SCROLL_SENSITIVE The constant indicating the type for a ResultSet object that is scrollable and generally sensitive to changes to the data that underlies the ResultSet.

View Code

  • resultSetConcurrency: 控制ResultSet的读/写并发类型

图片 20

ResultSet.CONCUR_READ_ONLY The constant indicating the concurrency mode for a ResultSet object that may NOT be updated.
ResultSet.CONCUR_UPDATABLE The constant indicating the concurrency mode for a ResultSet object that may be updated.

  从结果看,执行DDL语句展现受影响记录条数;执行DML展现插入、修改、删除的笔录条数;执行查询语句能够输出查询结果。

其它可更新的结果集还非得满足如下条件:
1) 全部数据都来源于一个表; 2)选出的数据集必须含有主键列;
如此那般, 获取的ResultSet正是可更新/可滚动的,
程序可透过调用ResultSet的updateXxx(int columnIndex/String columnLabel, X
x)方法来修改记录指针所针对的值, 最终调用updateRow()来交付修改.

  下面程序取得的SQL执行结果是未曾基于各列的数据类型调用相应的getXxx()方法,而是直接使用getString()方法来收获值,那是足以的。

SQLClient
public class SQLClient {
private Connection connection = null;
@Before
 public void setUp() {
 connection = ConnectionManger.getConnectionHikari("common.properties");
 }
@Test
 public void updateResultSet() throws SQLException {
 // 创建可更新,底层数据敏感的Statement
 try (
 PreparedStatement statement = connection.prepareStatement("SELECT * FROM t_ddl where id IN(?, ?)",
 ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE)
 ) {
 statement.setInt(1, 19);
 statement.setInt(2, 89);
 ResultSet rs = statement.executeQuery();
while (rs.next()) {
 System.out.printf("%s\t%s\t%s\t%n", rs.getInt(1), rs.getString(2), rs.getString(3));
 if (rs.getString("username").equals("name18")) {
 rs.updateString("username", "new_name_18");
 rs.updateRow();
 } else if (rs.getString("username").equals("name88")) {
 rs.updateString("username", "new_name_88");
 rs.updateRow();
 }
 }
 SQLUtil.displayResultSet(rs, 3);
 }
 }
@After
 public void tearDown() {
 try {
 connection.close();
 } catch (SQLException ignored) {
 }
 }
}

  ResultSet的getString()方法差不离能够获得除了Blob之外的任意档次列的值,因为具备的数据类型都得以活动转换到字符串类型。

  • SQLUtil

  使用PreparedStatement执行SQL语句:

    若经常要频仍实践一条组织相似的SQL语句,如下两条:

public static void displayResultSet(ResultSet result, int column) {
 try {
 result.beforeFirst();
 while (result.next()) {
 for (int i = 1; i <= column; ++i) {
 System.out.printf("%s\t", result.getObject(i));
 }
 System.out.printf("%s%n", result.getObject(column));
 }
 } catch (SQLException e) {
 throw new RuntimeException(e);
 }
}

    insert into student_table values(null, ‘张三’, 1);

ResultSetMetaData

    insert into student_table values(null, ‘李四’, 2);

ResultSet提供了getMetaData()方法来获得ResultSetMetaData以分析关于ResultSet的描述音讯(前面大家已经应用ResultSetMetaData来赢得结果集的列数以及列名):

    对于那两条语句,它们组织相似,只是履行插入时插入的值分化而已。对于那种状态,能够接纳占位符(?)参数的SQL语句来取代它:

ResultSetMetaData方法 描述
int getColumnCount() Returns the number of columns in this ResultSet object.
String getColumnLabel(int column) Gets the designated column’s suggested title for use in printouts and displays.
String getColumnName(int column) Get the designated column’s name.
int getColumnType(int column) Retrieves the designated column’s SQL type.
String getColumnTypeName(int column) Retrieves the designated column’s database-specific type name.
boolean isAutoIncrement(int column) Indicates whether the designated column is automatically numbered.

    insert into student_table values(null, ?, ?);

  • analyseResult

    JDBC提供了PreparedStatement接口,它是Statement的子接口。它能够开始展览预编写翻译SQL语句,预编写翻译后的SQL语句被贮存在PreparedStatement对象中,然后能够利用该

     对象往往神速地履行该语句。使用PreparedStatement比采取Statement的功能要高。

@Test
public void analyseResult() throws SQLException {
 try (
 PreparedStatement statement = connection.prepareStatement("SELECT * FROM t_ddl")
 ) {
 ResultSetMetaData meta = statement.executeQuery().getMetaData();
 for (int i = 1; i <= meta.getColumnCount(); ++i) {
 System.out.printf("label: %s, name: %s, type: %s%n", meta.getColumnLabel(i), meta.getColumnName(i), meta.getColumnTypeName(i));
 }
 }
}

    制造PreparedStatement对象使用Connection的prepareStatement()方法,该方法必要传入3个SQL字符串,该SQL字符串能够回顾占位符参数,如下:

注:
因为获取ResultSetMetaData只可以经过ResultSet的getMetaData()方法,所以选拔ResultSetMetaData就要求肯定的周转时支付;因而只要在编码过程中早已驾驭列数/列名/类型等消息,
就没有再用ResultSetMetaData了.

      //创制叁个PreparedStatement对象

RowSet

      pstmt = conn.prepareStatement(“insert into student_table
values(null,?,1)”);

javax.sql.RowSet继承自ResultSet, RowSet的子接口有CachedRowSet,
FilteredRowSet, JdbcRowSet, JoinRowSet, WebRowSet,
在那之中唯有JdbcRowSet须求有限支撑与数据库的总是, 别的都以离线RowSet.

    PreparedStatement也提供了execute()、executeUpdate()、executeQuery()多个措施来执行SQL语句,可是那八个艺术无需参数,因为PreparedStatement已经储存了预

图片 21

     编译的SQL语句。

与ResultSet相比较,
RowSet私下认可正是可滚动/可更新/可体系化的结果集,因而能够看作JavaBean使用(比如在互联网上传输,用于共同两端数据).
而对此离线RowSet,
程序在创制RowSet时已把多少从最底层数据库读取到了内部存储器,因而能够充足发挥内部存款和储蓄器的优势,下落数据库Server的载重,提升质量.

    使用PreparedStatement预编写翻译SQL语句时,该SQL语句能够带占位符参数,由此在执行SQL语句此前必须为这个参数字传送入参数值,PreparedStatement提供了一多级

RowSet接口提供了如下常用方法:

     的setXxx(int index, Xxx value)方法来传播参数值。

方法 描述
void setUrl(String url) Sets the URL this RowSet object will use when it uses the DriverManager to create a connection.
void setUsername(String name) Sets the username property for this RowSet object to the given String.
void setPassword(String password) Sets the database password for this RowSet object to the given String.
void setCommand(String cmd) Sets this RowSet object’s command property to the given SQL query.
void setXxx(String parameterName/int parameterIndex, X x)
void execute() Fills this RowSet object with data.

 上边程序示范了利用Statement和PreparedStatement分别插入100条记下的相持统一。:

Java 1.7为RowSet提供了RowSetProvider与RowSetFactory工具,
RowSetProvider负载创设RowSetFactory,
RowSetFactory提供如下方法制造RowSet实例:

图片 22图片 23

方法 描述
CachedRowSet createCachedRowSet() Creates a new instance of a CachedRowSet.
FilteredRowSet createFilteredRowSet() Creates a new instance of a FilteredRowSet.
JdbcRowSet createJdbcRowSet() Creates a new instance of a JdbcRowSet.
JoinRowSet createJoinRowSet() Creates a new instance of a JoinRowSet.
WebRowSet createWebRowSet() Creates a new instance of a WebRowSet.
 1 import java.io.FileInputStream;
 2 import java.util.Properties;
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.PreparedStatement;
 6 import java.sql.Statement;
 7 
 8 public class PreparedStatementTest{
 9     private String driver;
10     private String url;
11     private String user;
12     private String pass;
13     
14     public void initParam(String paramFile) throws Exception{
15         //使用Properties类加载属性文件
16         Properties props = new Properties();
17         props.load(new FileInputStream(paramFile));
18         driver = props.getProperty("driver");
19         url = props.getProperty("url");
20         user = props.getProperty("user");
21         pass = props.getProperty("pass");
22         //加载驱动
23         Class.forName(driver);
24     }
25     
26     public void insertUseStatement() throws Exception{
27         long start = System.currentTimeMillis();
28         try(
29             //获取数据库连接
30             Connection conn = DriverManager.getConnection(url, user, pass);
31             //使用Connection来创建一个Statement对象
32             Statement stmt = conn.createStatement())
33             {
34                 //需要使用100条SQL语句来插入100条记录
35                 for(int i = 0; i < 100; i++){
36                     stmt.executeUpdate("insert into student_table values("
37                         + "null,'姓名" + i + "', 1)");
38                 }
39                 System.out.println("使用Statement费时:"
40                     + (System.currentTimeMillis() - start));
41             }
42     }
43     
44     public void insertUsePrepare() throws Exception{
45         long start = System.currentTimeMillis();
46         try(
47             //获取数据库连接
48             Connection conn = DriverManager.getConnection(url, user, pass);
49             //使用Connection来创建一个PreparedStatement对象
50             PreparedStatement pstmt = conn.prepareStatement("insert into student_table values(null,?,1)"))
51             {
52                 //100次为PreparedStatement的参数设值,就可以插入100条记录
53                 for(int i = 0; i < 100; i++){
54                     pstmt.setString(1, "姓名" + i);
55                     pstmt.executeUpdate();
56                 }
57                 System.out.println("使用PreparedStatement费时:" + (System.currentTimeMillis() - start));
58             }
59     }
60     
61     public static void main(String[] args) throws Exception{
62         PreparedStatementTest pt = new PreparedStatementTest();
63         pt.initParam("mysql.ini");
64         pt.insertUseStatement();
65         pt.insertUsePrepare();
66     }
67 }

 

View Code

  • JdbcRowSetClient

图片 24

  从地点的结果看,PreparedStatement耗费时间少于Statement。

/**
 * @author jifang
 * @since 16/2/19 上午9:55.
 */
public class JdbcRowSetClient {
private JdbcRowSet set;
@Before
 public void setUp() throws IOException, SQLException, ClassNotFoundException {
 Properties config = SQLUtil.loadConfig("common.properties");
Class.forName(config.getProperty("mysql.driver.class"));
set = RowSetProvider.newFactory().createJdbcRowSet();
 set.setUrl(config.getProperty("mysql.url"));
 set.setUsername(config.getProperty("mysql.user"));
 set.setPassword(config.getProperty("mysql.password"));
 }
@Test
 public void select() throws SQLException {
 set.setCommand("select * from t_ddl");
 set.execute();
// 反向迭代
 set.afterLast();
 while (set.previous()) {
 System.out.printf("%d\t%s\t%s%n", set.getInt(1), set.getString(2), set.getString(3));
 if (set.getInt(1) == 187) {
 set.updateString("username", "new_188_name");
 set.updateRow();
 }
 }
 }
@After
 public void tearDown() {
 try {
 set.close();
 } catch (SQLException e) {
 }
 }
}

  使用PreparedStatement还有1个很好的机能——用于幸免SQL注入。

可将开始化RowSet操作封装成一个艺术:

上面以三个不难的登录窗口为例来介绍那种SQL注入的结果:

  • SQLUtil

图片 25图片 26

 1 import java.sql.Connection;
 2 import java.sql.DriverManager;
 3 import java.sql.Statement;
 4 import java.sql.ResultSet;
 5 import java.util.Properties;
 6 import java.io.FileInputStream;
 7 import java.awt.*;
 8 import javax.swing.*;
 9 
10 
11 public class LoginFrame{
12     private final String PROP_FILE = "mysql.ini";
13     private String driver;
14     //url是数据库的服务地址
15     private String url;
16     private String user;
17     private String pass;
18     //登录界面的GUI组件
19     private JFrame jf = new JFrame("登录");
20     private JTextField userField = new JTextField(20);
21     private JTextField passField = new JTextField(20);
22     private JButton loginButton = new JButton("登录");
23     
24     public void init() throws Exception{
25         Properties connProp = new Properties();
26         connProp.load(new FileInputStream(PROP_FILE));
27         driver = connProp.getProperty("driver");
28         url = connProp.getProperty("url");
29         user = connProp.getProperty("user");
30         pass = connProp.getProperty("pass");
31         //加载驱动
32         Class.forName(driver);
33         //为登录按钮添加事件监听器
34         loginButton.addActionListener(e -> {
35             //登录成功则显示“登录成功”
36             if(validate(userField.getText(), passField.getText())){
37                 JOptionPane.showMessageDialog(jf, "登录成功");
38             }else{
39                 //否则显示“登录失败”
40                 JOptionPane.showMessageDialog(jf, "登录失败");
41             }
42         });
43         jf.add(userField, BorderLayout.NORTH);
44         jf.add(passField);
45         jf.add(loginButton, BorderLayout.SOUTH);
46         jf.pack();
47         jf.setVisible(true);
48     }
49     
50     private boolean validate(String userName, String userPass){
51         //执行查询的SQL语句
52         String sql = "select * from jdbc_test "
53             + "where jdbc_name='" + userName
54             + "' and jdbc_desc='" + userPass + "';";
55         System.out.println(sql);
56         try(
57         Connection conn = DriverManager.getConnection(url, user, pass);
58         Statement stmt = conn.createStatement();
59         ResultSet rs = stmt.executeQuery(sql))
60         {
61             //若查询的ResultSet里有超过一条的记录,则登录成功
62             if(rs.next()){
63                 return true;
64             }
65         }catch(Exception e){
66             e.printStackTrace();
67         }
68         
69         return false;
70     }
71     
72     public static void main(String[] args) throws Exception{
73         new LoginFrame().init();
74     }
75 }
public static RowSet initRowSet(RowSet set, Properties config) {
 try {
 Class.forName(config.getProperty("mysql.driver.class"));
 set.setUrl(config.getProperty("mysql.url"));
 set.setUsername(config.getProperty("mysql.user"));
 set.setPassword(config.getProperty("mysql.password"));
return set;
 } catch (ClassNotFoundException | SQLException e) {
 throw new RuntimeException(e);
 }
}

View Code

离线RowSet

报到界面:

眼下查询获得ResultSet后务必及时处理,不然一经Connection连接关闭,再去读/写ResultSet就会掀起卓殊.而离线RowSet会一向将数据读入内部存储器,封装成RowSet对象,CachedRowSet是独具离线RowSet的父接口,提供了之类实用方法:

图片 27

方法 描述
void populate(ResultSet data) Populates this CachedRowSet object with data from the given ResultSet object.
void acceptChanges() Propagates row update, insert and delete changes made to this CachedRowSet object to the underlying data source.
void acceptChanges(Connection con) Propagates all row update, insert and delete changes to the data source backing this CachedRowSet object using the specified Connection object to establish a connection to the data source.

报到成功界面:

CachedRowSetClient

图片 28

/**
 * @author jifang
 * @since 16/2/19 上午10:32.
 */
public class CachedRowSetClient {
private CachedRowSet query(String config, String sql) {
/*Connection/Statement/ResultSet会自动关闭*/
 try (
 Connection connection = ConnectionManger.getConnectionHikari(config);
 Statement statement = connection.createStatement();
 ResultSet rs = statement.executeQuery(sql)
 ) {
 CachedRowSet rowSet = RowSetProvider.newFactory().createCachedRowSet();
 rowSet.populate(rs);
 return rowSet;
 } catch (SQLException e) {
 throw new RuntimeException(e);
 }
 }
@Test
 public void client() throws SQLException {
 CachedRowSet set = query("common.properties", "select * from t_ddl");
// 此时RowSet已离线
 while (set.next()) {
 System.out.printf("%s\t%s\t%s%n", set.getInt(1), set.getString(2), set.getString(3));
 if (set.getInt(1) == 3) {
 set.updateString(3, "new3_password3_3");
 set.updateRow();
 }
 }
// 重新获得连接
 Connection connection = ConnectionManger.getConnectionHikari("common.properties");
 connection.setAutoCommit(false);
 // 把对RowSet所做的修改同步到数据库
 set.acceptChanges(connection);
 }
}

图片 29

离线RowSet分页

去数据库中查询是或不是留存用户和密码,执行的SQL语句。从地方的结果能够看出,大家在签到用户名中输入‘
or true or ’时,竟然也登录成功了。原因就出在执行的SQL语句上。

是因为CachedRowSet会将底层数据库数据直接装载到内部存款和储蓄器,由此假设SQL查询再次来到数据过大,大概会促成内部存款和储蓄器溢出.为了缓解那么些题材,CachedRowSet提供了分页作用:
二遍只装载ResultSet的一对记录,那样能够幸免CachedRowSet占用内部存款和储蓄器过大.

若是密码和用户为空,可是where后的原则永远为真,那就报告软件,数据库中存在该用户,能够登录。

方法 描述
void populate(ResultSet rs, int startRow) Populates this CachedRowSet object with data from the given ResultSet object.
void setPageSize(int size) Sets the CachedRowSet object’s page-size.
boolean nextPage() Increments the current page of the CachedRowSet.
boolean previousPage() Decrements the current page of the CachedRowSet.

  把地点的validate()方法换到选择PreparedStatement来进行验证,而不是直接使用Statement:

CachedRowSetClient

图片 30图片 31

public class CachedRowSetClient {
@Test
 public void cachedRowSetPaging() throws SQLException {
int page = 4;
 int size = 10;
try (
 ResultSet rs = ConnectionManger.getConnectionHikari("common.properties")
 .createStatement()
 .executeQuery("SELECT * FROM t_ddl ORDER BY id")
 ) {
 CachedRowSet rowSet = RowSetProvider.newFactory().createCachedRowSet();
 rowSet.populate(rs, (page - 1) * size + 1);
 rowSet.setPageSize(size);
while (rowSet.nextPage()) {
 rowSet.next();
 displayRowSet(rowSet, 3);
 }
 }
 }
private void displayRowSet(RowSet set, int column) {
 try {
 for (int i = 1; i <= column; ++i) {
 System.out.printf("%s\t", set.getString(i));
 }
 System.out.println();
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
}
 1 import java.sql.Connection;
 2 import java.sql.DriverManager;
 3 import java.sql.PreparedStatement;
 4 import java.sql.ResultSet;
 5 import java.util.Properties;
 6 import java.io.FileInputStream;
 7 import java.awt.*;
 8 import javax.swing.*;
 9 
10 
11 public class LoginFrame{
12     private final String PROP_FILE = "mysql.ini";
13     private String driver;
14     //url是数据库的服务地址
15     private String url;
16     private String user;
17     private String pass;
18     //登录界面的GUI组件
19     private JFrame jf = new JFrame("登录");
20     private JTextField userField = new JTextField(20);
21     private JTextField passField = new JTextField(20);
22     private JButton loginButton = new JButton("登录");
23     
24     public void init() throws Exception{
25         Properties connProp = new Properties();
26         connProp.load(new FileInputStream(PROP_FILE));
27         driver = connProp.getProperty("driver");
28         url = connProp.getProperty("url");
29         user = connProp.getProperty("user");
30         pass = connProp.getProperty("pass");
31         //加载驱动
32         Class.forName(driver);
33         //为登录按钮添加事件监听器
34         loginButton.addActionListener(e -> {
35             //登录成功则显示“登录成功”
36             if(validate(userField.getText(), passField.getText())){
37                 JOptionPane.showMessageDialog(jf, "登录成功");
38             }else{
39                 //否则显示“登录失败”
40                 JOptionPane.showMessageDialog(jf, "登录失败");
41             }
42         });
43         jf.add(userField, BorderLayout.NORTH);
44         jf.add(passField);
45         jf.add(loginButton, BorderLayout.SOUTH);
46         jf.pack();
47         jf.setVisible(true);
48     }
49     
50     private boolean validate(String userName, String userPass){
51         //执行查询的SQL语句
52         String sql = "select * from jdbc_test "
53             + "where jdbc_name='" + userName
54             + "' and jdbc_desc='" + userPass + "';";
55         System.out.println(sql);
56         try(
57         Connection conn = DriverManager.getConnection(url, user, pass);
58         PreparedStatement pstmt = conn.prepareStatement("select * from jdbc_test where jdbc_name=? and jdbc_desc=?;"))
59         {
60             pstmt.setString(1, userName);
61             pstmt.setString(2, userPass);
62             try(
63                 ResultSet rs = pstmt.executeQuery())
64                 {
65                     //若查询的ResultSet里有超过一条的记录,则登录成功
66                     if(rs.next()){
67                         return true;
68                     }
69                 }
70         }catch(Exception e){
71             e.printStackTrace();
72         }
73         
74         return false;
75     }
76     
77     public static void main(String[] args) throws Exception{
78         new LoginFrame().init();
79     }
80 }

事务

View Code

事情是由一步/几步数据库操作体系组成的逻辑执行单元, 这几个操作依然全体进行,
要么全体不执行.

签到界面:

注: MySQL事务功效必要有InnoDB存款和储蓄引擎的支撑,
详见MySQL存款和储蓄引擎InnoDB与Myisam的首要分歧.

图片 32

ACID特性

签到退步界面:

  • 原子性(A: Atomicity): 事务是不可再分的蝇头逻辑执行体;
  • 一致性(C: Consistency): 事务执行的结果,
    必须使数据库从1个一致性状态, 变为另三个一致性状态.
  • 隔绝性(I: Isolation): 种种业务的施行互不干扰,
    任意3个工作的里边操作对别的并发事务都以隔开的(并发执行的政工之间不能够见到对方的中间状态,无法互相影响)
  • 绵延(D: Durability): 持续性也称持久性(Persistence),
    指事务一旦付出,
    对数据所做的其它改变都要记录到千古存款和储蓄器(平常指物理数据库).

图片 33

Commit/Rollback

图片 34

  • 当事务所包罗的全部操作都事业有成实施后交给业务,使操作永久生效,事务提交有二种方式:

  从结果中能够看来,把用户中的’ or true or
‘添加到了jdbc_name的后面,避免的SQL注入。

1). 显式提交: 使用commit;
2). 自动提交: 执行DDL/DCL语句或程序正常化退出;

  使用PreparedStatement比使用Statement多了如下七个便宜:

  • 当事务所包括的轻易2个操作实践破产后应当回滚事务,
    使该事务中所做的修改总体失效, 事务回滚也有两种情势:

    1.PreparedStatement预编译SQL语句,品质更好

1). 显式回滚: 使用rollback;
2). 自动回滚: 系统错误或粗野退出.

    2.PreparedStatement无需“拼接”SQL语句,编制程序更简约

瞩目: 同一事务中兼有的操作,都必须运用同三个Connection.

    3.PreparedStatement得以防范SQL注入,安全性更好

JDBC支持

  基于上边三点,平时推荐防止采用Statement来进行SQL语句,改为使用PreparedStatement执行SQL语句。

JDBC对事情的支撑由Connection提供,
Connection暗中同意打开自动提交,即关闭工作,SQL语句一旦实施,
便会霎时付给数据库,永久生效,无法对其开始展览回滚操作,由此供给关闭自动提交功效.

  使用PreparedStatement执行带占位符参数的SQL语句时,SQL语句中的占位符参数只好代替普通值,无法代表表名、列名等数据库对象,也无法替代的insert、select等重点字

  • 率先创制一张表用于测试

  使用CallableStatement调用存款和储蓄进度:

    图片 35

CREATE TABLE `account` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(45) NOT NULL,
 `money` decimal(10,0) unsigned zerofill NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `name_UNIQUE` (`name`),
 UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=UTF8;

  进入八个数据库中,执行上面的吩咐。delimiter
//将MySQL的语句甘休符改为双斜线(\\),那样就足以在创制存款和储蓄进程中选取分号作为分隔符(MySQL暗中认可使用分号作为语

  • 插入两条测试数据

   句甘休符)。记得执行完上边命令再将竣事符改为分行。上边命令成立了名为add_pro的积存进程,该存储进程包括四个参数:a
b是流传参数,sum使用out修饰,是传播

   参数

INSERT INTO `account` (`name`, `money`) VALUES ('feiqing', '7800');
INSERT INTO `account` (`name`, `money`) VALUES ('xiaofang', '7800');

  调用存款和储蓄进程使用CallableStatement,可经过Connection的prepareCall()方法来成立CallableStatement对象,创设该对象时索要传入调用存款和储蓄进度的SQL语句。

  • No Transaction

  调用存款和储蓄进程的SQL语句格式:{call 进程名(?, ?, …, ?)}若下所示:

    //使用Connection来创立三个CallableStatement对象

/**
 * @author jifang
 * @since 16/2/19 下午5:02.
 */
public class TransactionClient {
private Connection connection = ConnectionManger.getConnection("common.properties");

 @Test
 public void noTransaction() throws SQLException {
 try (
 PreparedStatement minusSM = connection.prepareStatement("UPDATE `account` SET `money`=(`money` - ?) WHERE `name`=?");
 PreparedStatement addSM = connection.prepareStatement("UPDATE `account` SET `money`=(`money` + ?) WHERE `name`=?")
 ) {
 // 从feiqing账户转出
 minusSM.setBigDecimal(1, new BigDecimal(100));
 minusSM.setString(2, "feiqing");
 minusSM.execute();
// 中途抛出异常, 会导致两账户前后不一致
 if (true){
 throw new RuntimeException("no-transaction");
 }
// 转入xiaofang账户
 addSM.setBigDecimal(1, new BigDecimal(100));
 addSM.setString(2, "xiaofang");
 addSM.execute();
 }
 }
@After
 public void tearDown() {
 try {
 connection.close();
 } catch (SQLException e) {
 }
 }
}

    cstmt = conn.prepareCall(“{call add_pro(?, ?, ?)”});

  • By Transaction

  存款和储蓄进程有传播参数,也有扩散参数。Java程序必须为这几个参数字传送入值,可经过CallableStatement的setXxx()方法为流传参数设置值;传出参数正是Java程序能够由此该参数

   获取存款和储蓄进程里的值,CallableStatement要求调用registerOutParameter()方法来注册该参数。如下所示:

@Test
public void byTransaction() throws SQLException {
boolean autoCommitFlag = connection.getAutoCommit();
 // 关闭自动提交, 开启事务
 connection.setAutoCommit(false);
try (
 PreparedStatement minusSM = connection.prepareStatement("UPDATE `account` SET `money`=(`money` - ?) WHERE `name`=?");
 PreparedStatement addSM = connection.prepareStatement("UPDATE `account` SET `money`=(`money` + ?) WHERE `name`=?")
 ) {
 // 从feiqing账户转出
 minusSM.setBigDecimal(1, new BigDecimal(100));
 minusSM.setString(2, "feiqing");
 minusSM.execute();
// 中途抛出异常: rollback
 if (true) {
 throw new RuntimeException("no-transaction");
 }
// 转入xiaofang账户
 addSM.setBigDecimal(1, new BigDecimal(100));
 addSM.setString(2, "xiaofang");
 addSM.execute();
 connection.commit();
 } catch (Throwable e) {
 connection.rollback();
 throw new RuntimeException(e);
 } finally {
 connection.setAutoCommit(autoCommitFlag);
 }
}

    //注册CallableStatement的第陆个参数是int类型

只顾: 当Connection境遇贰个未处理的SQLException时,
程序将会狼狈退出,事务也会自动回滚;但若是程序捕获了该尤其,
则要求在老大处理块中显式地回滚事务.

    cstmt.registerOutParameter(3, Types.INTEGER);

隔离级别

  经过地点步骤,就足以调用CallableStatement的execute()方法来推行存款和储蓄进程了,执行落成后经过CallableStatement对象的getXxx(int
index)方法来取得内定传出参数的值。

在平等数量环境下,使用同一输入,执行同一操作,依据不一致的割裂级别,会促成不一样的结果.不相同的作业隔断级别能够缓解的数码出现难题的力量是差异的,
由弱到强分为以下四级:

图片 36图片 37

隔离级别 描述 释义
read uncommitted 读未提交数据 不符合原子性,称为”脏读”, 在实际业务中不用.
read commited 读已提交数据(Oracle) 事务执行中,读不到另一个事务正在进行的操作,但可以读到另一个事务结束的操作.
repeatable read 可重复读(MySQL) 在事务执行中,所有信息都来自事务开始的那一瞬间的信息,不受其他已提交事务的影响.
serializeable 串行化 所有的事务按顺序执行,也就没有了冲突的可能.隔离级别最高,但事务相互等待时间太长,性能最差,少用.
 1 import java.util.Properties;
 2 import java.io.FileInputStream;
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.CallableStatement;
 6 import java.sql.Types;
 7 
 8 public class CallableStatementTest{
 9     private String driver;
10     private String url;
11     private String user;
12     private String pass;
13     
14     public void initParam(String paramFile) throws Exception{
15         //使用Properties类来加载属性文件
16         Properties props = new Properties();
17         props.load(new FileInputStream(paramFile));
18         driver = props.getProperty("driver");
19         url = props.getProperty("url");
20         user = props.getProperty("user");
21         pass = props.getProperty("pass");
22     }
23     
24     public void callProcedure()throws Exception{
25         //加载驱动
26         Class.forName(driver);
27         try(
28             //获取数据库连接
29             Connection conn = DriverManager.getConnection(url, user, pass);
30             //使用Connection来创建一个CallableStatement对象
31             CallableStatement cstmt = conn.prepareCall("{call add_pro(?,?,?)}")
32         ){
33             cstmt.setInt(1, 4);
34             cstmt.setInt(2, 5);
35             //注册CallableStatement的第三个参数时int类型
36             cstmt.registerOutParameter(3, Types.INTEGER);
37             //执行存储过程
38             cstmt.execute();
39             //获取并输出存储过程传出的参数的值
40             System.out.println("执行结果是:" + cstmt.getInt(3));
41         }
42     }
43     
44     public static void main(String[] args) throws Exception{
45         CallableStatementTest ct = new CallableStatementTest();
46         ct.initParam("mysql.ini");
47         ct.callProcedure();
48     }
49 }
  • MySQL设置工作隔断级别:

View Code

set session transaction isolation level [read uncommitted | read
committed | repeatable read |serializable]
查阅当前事情隔开级别:
select @@tx_isolation

图片 38

  • JDBC设置隔开级别

   管理结果集:

connection.setTransactionIsolation(int level)
level可为以下值:
1). Connection.TRANSACTION_READ_UNCOMMITTED
2). Connection.TRANSACTION_READ_COMMITTED
3). Connection.TRANSACTION_REPEATABLE_READ
4). Connection.TRANSACTION_SERIALIZABLE

    JDBC使用ResultSet来封装执行查询获得的询问结果,后通过移动ResultSet的记录指针来取出结果集内容。除此之外,JDBC还同意ResultSet来更新记录,并提供

附: 事务并发读难题

     ResultSetMetaData来获得ResultSet对象的相干消息

  1. 脏读(dirty read):读到另八个业务的未提交的多少,即读取到了脏数据(read
    commited级别可消除).
  2. 不可重复读(unrepeatable
    read):对同样记录的五回读取不雷同,因为另一政工对该记录做了修改(repeatable
    read级别可缓解)
  3. 幻读/虚读(phantom
    read):对同一张表的两回查询区别,因为另一工作插入了一条记下(repeatable
    read级别可解决)

    可滚动、可更新的结果集:

  • 不得重复读和幻读的分别:

      使用absolute()、previous()、afterLast()等办法自由活动记录指针的ResultSet被叫做可滚动的结果集。

  1. 不足重复读是读取到了另一工作的更新;
  2. 幻读是读取到了另一事情的插入(MySQL中无法测试到幻读,效果与不足重复读一致);

      以暗中同意情势打开的ResultSet是不可更新的,若希望创制可更新的ResultSet,则必须在开立Statement或PreparedStatement时传出额外的参数。

此外有关并发事务难题可参照<数据库事务并发带来的标题>

      Connection在创设Statement或PreparedStatement时可额外传入如下多个参数:

批处理

       1.resultSetType:控制ResultSet的品类,该参数能够取如下两个值:

多条SQL语句被看成同一批操作同时执行.
调用Statement对象的addBatch(String sql)方法将多条SQL语句收集起来,
然后调用executeBatch()同时执行.
为了让批量操作能够正确实行, 必须把批处理视为单个事务,
假若在履行进度中破产, 则让事情回滚到批处理起来前的状态.

        1.ResultSet.TYPE_FORWARD_ONLY:该常量控制记录指针只可以前进挪动。

public class SQLClient {
private Connection connection = null;
 private Random random = new Random();
@Before
 public void setUp() {
 connection = ConnectionManger.getConnectionHikari("common.properties");
 }
@Test
 public void updateBatch() throws SQLException {
 List<String> sqlList = Lists.newArrayListWithCapacity(10);
 for (int i = 0; i < 10; ++i) {
 sqlList.add("INSERT INTO user(name, password) VALUES('student" + i + "','" + encodeByMd5(random.nextInt() + "") + "')");
 }
 int[] results = update(connection, sqlList);
 for (int result : results) {
 System.out.printf("%d ", result);
 }
 }
private int[] update(Connection connection, List<String> sqlList) {
boolean autoCommitFlag = false;
 try {
 autoCommitFlag = connection.getAutoCommit();
// 关闭自动提交, 打开事务
 connection.setAutoCommit(false);
// 收集SQL语句
 Statement statement = connection.createStatement();
 for (String sql : sqlList) {
 statement.addBatch(sql);
 }
// 批量执行 & 提交事务
 int[] result = statement.executeBatch();
 connection.commit();
return result;
 } catch (SQLException e) {
 try {
 connection.rollback();
 } catch (SQLException ignored) {
 }
 throw new RuntimeException(e);
 } finally {
 try {
 connection.setAutoCommit(autoCommitFlag);
 } catch (SQLException ignored) {
 }
 }
 }
private String encodeByMd5(String input) {
 try {
 MessageDigest md5 = MessageDigest.getInstance("MD5");
 BASE64Encoder base64Encoder = new BASE64Encoder();
 return base64Encoder.encode(md5.digest(input.getBytes("utf-8")));
 } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
 throw new RuntimeException(e);
 }
 }
@After
 public void tearDown() {
 try {
 connection.close();
 } catch (SQLException ignored) {
 }
 }
}

        2.ResultSet.TYPE_SCROLL_INSENSITIVE:该常量控制记录指针能够无限制活动(可滚动结果集),但底层数据的改变不会影响ResultSet的剧情

注:
1).
对于批处理,也足以行使PreparedStatement,建议利用Statement,因为PreparedStatement的预编写翻译空间有限,当数据量过大时,恐怕会挑起内部存款和储蓄器溢出.
2).
MySQL暗中同意也并未打开批处理效果,要求在UPAJEROL中设置rewriteBatchedStatements=true参数打开.

        3.ResultSet.TYPE_SCROLL_SENSITIVE:该常量控制记录指针可自由运动(可滚动结果集),而且底层数据的变更会影响ResultSet的内容

DbUtils

        TYPE_SCROLL_INSENSITIVE、TYPE_SCROLL_SENSITIVE四个常量的功效须求底层数据库驱动的协助,对于有个别数据库驱动来说,那八个并没有太大的区分

commons-dbutils是Apache
康芒斯组件中的一员,提供了对JDBC的简便包装,以简化JDBC编制程序;使用dbutils供给在pom.xml中添加如下依赖:

       2.resultSetConcurrency:控制ResultSet并发类型,该参数能够接到如下多少个值:

<dependency>
 <groupId>commons-dbutils</groupId>
 <artifactId>commons-dbutils</artifactId>
 <version>1.6</version>
</dependency>

        1.ResultSet.CONCUR_READ_ONLY:该常量提示ResultSet是只读的产出情势(暗中认可)。

dbutils的常用类/接口如下:

        2.ResultSet.CONCUR_UPDATABLE:该常量提示ResultSet是可更新的面世形式。

  1. DbUtils: 提供了一连串的实用静态方法(如:close());
  2. ResultSetHandler: 提供对结果集ResultSet与JavaBean等的转移;
  3. QueryRunner:

      上面代码通过那八个参数制造了二个PreparedStatement对象,由该对象生成的ResultSet对象将是可滚动、可更新的结果集:

  • update()(执行insert/update/delete)
  • query()(执行select)
  • batch()(批处理).

      //使用Connection创造一个PreparedStatement对象

QueryRunner更新

      //传入控制结果集可滚动、可更新的参数:

常用的update方法签名如下:

      pstmt = conn.prepareStatement(sql,
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

int update(String sql, Object... params);
int update(Connection conn, String sql, Object... params);

/**
 * @author jifang
 * @since 16/2/20 上午10:25.
 */
public class QueryRunnerClient {
@Test
 public void update() throws SQLException {
 QueryRunner runner = new QueryRunner(ConnectionManger.getDataSourceHikari("common.properties"));
 String sql = "INSERT INTO t_ddl(username, password) VALUES(?, ?)";
 runner.update(sql, "fq", "fq_password");
 }
}

      需求提议的是,可更新的结果集还亟需满意如下多个规范:

第2种格局必要提供Connection, 那样往往调用update能够共用三个Connection,
因而调用该方法能够支撑工作;

        1.全数数据都应该来自二个表

QueryRunner查询

        2.选出的数量集必须包括主键列

QueryRunner常用的query方法签名如下:

      可调用ResultSet的updateXxx(intcolumnIndex, Xxx
value)方法来修改记录指针所指记录、特定列的值,最终调用ResultSet的updateRow()方法来交给修改。

<T> T query(String sql, ResultSetHandler<T> rsh, Object... params);
<T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params);

  下边程序示范了那种创设可滚动、可更新的结果集的点子:

query()方法会通过sql语句和params参数查询出ResultSet,然后经过ResultSetHandler将ResultSet转换来对应的JavaBean再次来到.

图片 39图片 40

public class QueryRunnerClient {
// ...
@Test
 public void select() throws SQLException {
 QueryRunner runner = new QueryRunner();
 String sql = "SELECT * FROM t_ddl WHERE id = ?";
 TDDL result = runner.query(ConnectionManger.getConnectionHikari("common.properties"), sql, rsh, 7);
 System.out.println(result);
 }
private ResultSetHandler<TDDL> rsh = new ResultSetHandler<TDDL>() {
 @Override
 public TDDL handle(ResultSet rs) throws SQLException {
 TDDL tddl = new TDDL();
 if (rs.next()) {
 tddl.setId(rs.getInt(1));
 tddl.setUsername(rs.getString(2));
 tddl.setPassword(rs.getString(3));
 }
 return tddl;
 }
 };
private static class TDDL {
 private Integer id;
private String username;
private String password;
public Integer getId() {
 return id;
 }
public void setId(Integer id) {
 this.id = id;
 }
public String getUsername() {
 return username;
 }
public void setUsername(String username) {
 this.username = username;
 }
public String getPassword() {
 return password;
 }
public void setPassword(String password) {
 this.password = password;
 }
@Override
 public String toString() {
 return "TDDL{" +
 "id=" + id +
 ", username='" + username + '\'' +
 ", password='" + password + '\'' +
 '}';
 }
 }
}
 1 import java.util.Properties;
 2 import java.io.FileInputStream;
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.PreparedStatement;
 6 import java.sql.ResultSet;
 7 
 8 public class ResultSetTest{
 9     private String driver;
10     private String url;
11     private String user;
12     private String pass;
13     public void initParam(String paramFile) throws Exception{
14         //使用Properties类加载属性文件
15         Properties props = new Properties();
16         props.load(new FileInputStream(paramFile));
17         driver = props.getProperty("driver");
18         url = props.getProperty("url");
19         user = props.getProperty("user");
20         pass = props.getProperty("pass");
21     }
22     
23     public void query(String sql) throws Exception{
24         //加载驱动
25         Class.forName(driver);
26         try(
27             //获取数据库连接
28             Connection conn = DriverManager.getConnection(url, user, pass);
29             //使用Connection来创建一个PreparedStatement对象
30             //传入控制结果集可滚动、可更新的参数
31             PreparedStatement pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
32             ResultSet rs = pstmt.executeQuery()
33         ){
34             rs.last();
35             int rowCount = rs.getRow();
36             for(int i = rowCount; i > 0; i--){
37                 rs.absolute(i);
38                 System.out.println(rs.getString(1) + "\t"
39                     + rs.getString(2) + "\t" + rs.getString(3));
40                     //修改记录指针所指记录、第2列的值
41                     rs.updateString(2, "学生名" + i);
42                     //提交修改
43                     rs.updateRow();
44             }
45         }
46     }
47     
48     public static void main(String[] args) throws Exception{
49         ResultSetTest rt = new ResultSetTest();
50         rt.initParam("mysql.ini");
51         rt.query("select * from student_table");
52     }
53 }

ResultSetHandler

View Code

在上例中, 我们应用自定的ResultSetHandler将ResultSet转换来JavaBean,
但实际上dbutils暗中同意已经提供了众多概念出色的Handler完结:

图片 41

  1. BeanHandler :
    单行处理器,将ResultSet转换来JavaBean;
  2. BeanListHandler :
    多行处理器,将ResultSet转换来List<JavaBean>;
  3. MapHandler :
    单行处理器,将ResultSet转换到Map<String,Object>,
    列名为键;
  4. MapListHandler :
    多行处理器,将ResultSet转换到List<Map<String,Object>>;
  5. ScalarHandler :
    单行单列处理器,将ResultSet转换来Object(如保存SELECT COUNT(*) FROM
    t_ddl).
  6. ColumnListHandler
    :
    多行单列处理器,将ResultSet转换来List<Object>(使用时供给钦赐某一列的名目/编号,如new
    ColumListHandler(“name”):表示把name列数据放到List中);

图片 42

    student_table表中著录被倒序输出,且当程序运维甘休后,student_table表中拥有记录的student_name列的值都被涂改。

public class QueryRunnerClient {
private QueryRunner runner = new QueryRunner(ConnectionManger.getDataSourceHikari("common.properties"));
@Test
 public void clientBeanHandler() throws SQLException {
 String sql = "SELECT * FROM t_ddl WHERE id = ?";
 TDDL result = runner.query(sql, new BeanHandler<>(TDDL.class), 7);
 System.out.println(result);
 }
@Test
 public void clientBeanListHandler() throws SQLException {
 String sql = "SELECT * FROM t_ddl";
 List<TDDL> result = runner.query(sql, new BeanListHandler<>(TDDL.class));
 System.out.println(result);
 }
@Test
 public void clientScalarHandler() throws SQLException {
 String sql = "SELECT COUNT(*) FROM t_ddl";
 Long result = runner.query(sql, new ScalarHandler<Long>());
 System.out.println(result);
 }
@Test
 public void clientColumnListHandler() throws SQLException {
 String sql = "SELECT * FROM t_ddl";
 List<String> query = runner.query(sql, new ColumnListHandler<String>("username"));
 for (String i : query) {
 System.out.printf("%n%s", i);
 }
 }
}

    若要成立可更新的结果集,则使用查询语句询问的数目一般只可以来自于一个数据表,而且查询结果集中的数据列必须含有主键列,不然会引起更新战败。

QueryRunner批处理

  处理Blob类型数据:

QueryRunner提供了批处理措施int[] batch(String sql, Object[][]
params)(由于更新一行时必要Object[] param作为参数,
因此批处理须求钦定Object[][] params,当中种种Object[]对应一条记下):

    Blob(Binary Long
Object):是二进制长对象,Blob列常用于存款和储蓄大文件,典型的Blob内容是一张图纸或一个声音文件,由于它们的特殊性,必须选取特殊的形式来存款和储蓄

public class QueryRunnerClient {
private QueryRunner runner = new QueryRunner(ConnectionManger.getDataSourceHikari("common.properties"));
private Random random = new Random();
@Test
 public void clientBeanHandler() throws SQLException {
String sql = "INSERT INTO t_ddl(username, password) VALUES(?, ?)";
 int count = 46;
 Object[][] params = new Object[count][];
 for (int i = 0; i < count; ++i) {
 params[i] = new Object[]{"student-" + i, "password-" + random.nextInt()};
 }
runner.batch(sql, params);
 }
}

    使用Blob列能够把图纸、声音等文件的二进制数据保存在数据库中,并得以从数据库中还原钦命文件。

    若要求将图片插入数据库,显明不能够一向通过常备的SQL语句来达成,因为有一个关键难点——Blob常量无法代表。所以将Blob数据插入数据库要求选拔

     PreparedStatement,该对象有一个办法:setBinaryStream(int
parameterIndex, InputStream
x),该方法能够为钦点参数字传送入二进制输入流,从而得以完成将Blob数据保存

     到数据库的作用。

    必要从ResultSet里取出Blob数据时,能够调用ResultSet的getBlob(int
columnIndex)方法,该格局将回到贰个Blob对象,Blob对象提供了getBinaryStream()方法来获取该

     Blob数据的输入流,也足以动用Blob对象提供的getBytes()方法直接取出该Blob对象封装的二进制数据。

    为了把图纸放入数据库,使用如下SQL语句建立三个数据表:

图片 43

    img_data
mediumblob;创立三个mediumblob类型的数据列,用于保存图片数据

    mediumblob类型可存款和储蓄16M内容,blob类型可存款和储蓄64KB内容。

下边程序能够完成图片“上传”——实际上正是将图纸保存到数据库,并在左边的列表框中展现图片的名字,当用户双击列表框中的图片名时,左侧窗口将显示该图形——实质正是根据选中的ID从数据库里寻找图片,并将其出示出来:

图片 44图片 45

  1 import java.sql.*;
  2 import javax.swing.*;
  3 import java.awt.*;
  4 import java.awt.event.*;
  5 import java.util.Properties;
  6 import java.util.ArrayList;
  7 import java.io.*;
  8 import javax.swing.filechooser.FileFilter;
  9 
 10 public class BlobTest
 11 {
 12     JFrame jf = new JFrame("图片管理程序");
 13     private static Connection conn;
 14     private static PreparedStatement insert;
 15     private static PreparedStatement query;
 16     private static PreparedStatement queryAll;
 17     // 定义一个DefaultListModel对象
 18     private DefaultListModel<ImageHolder> imageModel
 19         = new DefaultListModel<>();
 20     private JList<ImageHolder> imageList = new JList<>(imageModel);
 21     private JTextField filePath = new JTextField(26);
 22     private JButton browserBn = new JButton("...");
 23     private JButton uploadBn = new JButton("上传");
 24     private JLabel imageLabel = new JLabel();
 25     // 以当前路径创建文件选择器
 26     JFileChooser chooser = new JFileChooser(".");
 27     // 创建文件过滤器
 28     ExtensionFileFilter filter = new ExtensionFileFilter();
 29     static
 30     {
 31         try
 32         {
 33             Properties props = new Properties();
 34             props.load(new FileInputStream("mysql.ini"));
 35             String driver = props.getProperty("driver");
 36             String url = props.getProperty("url");
 37             String user = props.getProperty("user");
 38             String pass = props.getProperty("pass");
 39             Class.forName(driver);
 40             // 获取数据库连接
 41             conn = DriverManager.getConnection(url , user , pass);
 42             // 创建执行插入的PreparedStatement对象,
 43             // 该对象执行插入后可以返回自动生成的主键
 44             insert = conn.prepareStatement("insert into img_table"
 45                 + " values(null,?,?)" , Statement.RETURN_GENERATED_KEYS);
 46             // 创建两个PreparedStatement对象,用于查询指定图片,查询所有图片
 47             query = conn.prepareStatement("select img_data from img_table"
 48                 + " where img_id=?");
 49             queryAll = conn.prepareStatement("select img_id, "
 50                 + " img_name from img_table");
 51         }
 52         catch (Exception e)
 53         {
 54             e.printStackTrace();
 55         }
 56     }
 57     public void init()throws SQLException
 58     {
 59         // -------初始化文件选择器--------
 60         filter.addExtension("jpg");
 61         filter.addExtension("jpeg");
 62         filter.addExtension("gif");
 63         filter.addExtension("png");
 64         filter.setDescription("图片文件(*.jpg,*.jpeg,*.gif,*.png)");
 65         chooser.addChoosableFileFilter(filter);
 66         // 禁止“文件类型”下拉列表中显示“所有文件”选项。
 67         chooser.setAcceptAllFileFilterUsed(false);
 68         // ---------初始化程序界面---------
 69         fillListModel();
 70         filePath.setEditable(false);
 71         // 只能单选
 72         imageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 73         JPanel jp = new JPanel();
 74         jp.add(filePath);
 75         jp.add(browserBn);
 76         browserBn.addActionListener(event -> {
 77             // 显示文件对话框
 78             int result = chooser.showDialog(jf , "浏览图片文件上传");
 79             // 如果用户选择了APPROVE(赞同)按钮,即打开,保存等效按钮
 80             if(result == JFileChooser.APPROVE_OPTION)
 81             {
 82                 filePath.setText(chooser.getSelectedFile().getPath());
 83             }
 84         });
 85         jp.add(uploadBn);
 86         uploadBn.addActionListener(avt -> {
 87             // 如果上传文件的文本框有内容
 88             if (filePath.getText().trim().length() > 0)
 89             {
 90                 // 将指定文件保存到数据库
 91                 upload(filePath.getText());
 92                 // 清空文本框内容
 93                 filePath.setText("");
 94             }
 95         });
 96         JPanel left = new JPanel();
 97         left.setLayout(new BorderLayout());
 98         left.add(new JScrollPane(imageLabel) , BorderLayout.CENTER);
 99         left.add(jp , BorderLayout.SOUTH);
100         jf.add(left);
101         imageList.setFixedCellWidth(160);
102         jf.add(new JScrollPane(imageList) , BorderLayout.EAST);
103         imageList.addMouseListener(new MouseAdapter()
104         {
105             public void mouseClicked(MouseEvent e)
106             {
107                 // 如果鼠标双击
108                 if (e.getClickCount() >= 2)
109                 {
110                     // 取出选中的List项
111                     ImageHolder cur = (ImageHolder)imageList.
112                     getSelectedValue();
113                     try
114                     {
115                         // 显示选中项对应的Image
116                         showImage(cur.getId());
117                     }
118                     catch (SQLException sqle)
119                     {
120                         sqle.printStackTrace();
121                     }
122                 }
123             }
124         });
125         jf.setSize(620, 400);
126         jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
127         jf.setVisible(true);
128     }
129     // ----------查找img_table填充ListModel----------
130     public void fillListModel()throws SQLException
131     {
132 
133         try(
134             // 执行查询
135             ResultSet rs = queryAll.executeQuery())
136         {
137             // 先清除所有元素
138             imageModel.clear();
139             // 把查询的全部记录添加到ListModel中
140             while (rs.next())
141             {
142                 imageModel.addElement(new ImageHolder(rs.getInt(1)
143                     ,rs.getString(2)));
144             }
145         }
146     }
147     // ---------将指定图片放入数据库---------
148     public void upload(String fileName)
149     {
150         // 截取文件名
151         String imageName = fileName.substring(fileName.lastIndexOf('\\')
152             + 1 , fileName.lastIndexOf('.'));
153         File f = new File(fileName);
154         try(
155             InputStream is = new FileInputStream(f))
156         {
157             // 设置图片名参数
158             insert.setString(1, imageName);
159             // 设置二进制流参数
160             insert.setBinaryStream(2, is , (int)f.length());
161             int affect = insert.executeUpdate();
162             if (affect == 1)
163             {
164                 // 重新更新ListModel,将会让JList显示最新的图片列表
165                 fillListModel();
166             }
167         }
168         catch (Exception e)
169         {
170             e.printStackTrace();
171         }
172     }
173     // ---------根据图片ID来显示图片----------
174     public void showImage(int id)throws SQLException
175     {
176         // 设置参数
177         query.setInt(1, id);
178         try(
179             // 执行查询
180             ResultSet rs = query.executeQuery())
181         {
182             if (rs.next())
183             {
184                 // 取出Blob列
185                 Blob imgBlob = rs.getBlob(1);
186                 // 取出Blob列里的数据
187                 ImageIcon icon=new ImageIcon(imgBlob.getBytes(1L
188                     ,(int)imgBlob.length()));
189                 imageLabel.setIcon(icon);
190             }
191         }
192     }
193     public static void main(String[] args)throws SQLException
194     {
195         new BlobTest().init();
196     }
197 }
198 // 创建FileFilter的子类,用以实现文件过滤功能
199 class ExtensionFileFilter extends FileFilter
200 {
201     private String description = "";
202     private ArrayList<String> extensions = new ArrayList<>();
203     // 自定义方法,用于添加文件扩展名
204     public void addExtension(String extension)
205     {
206         if (!extension.startsWith("."))
207         {
208             extension = "." + extension;
209             extensions.add(extension.toLowerCase());
210         }
211     }
212     // 用于设置该文件过滤器的描述文本
213     public void setDescription(String aDescription)
214     {
215         description = aDescription;
216     }
217     // 继承FileFilter类必须实现的抽象方法,返回该文件过滤器的描述文本
218     public String getDescription()
219     {
220         return description;
221     }
222     // 继承FileFilter类必须实现的抽象方法,判断该文件过滤器是否接受该文件
223     public boolean accept(File f)
224     {
225         // 如果该文件是路径,接受该文件
226         if (f.isDirectory()) return true;
227         // 将文件名转为小写(全部转为小写后比较,用于忽略文件名大小写)
228         String name = f.getName().toLowerCase();
229         // 遍历所有可接受的扩展名,如果扩展名相同,该文件就可接受。
230         for (String extension : extensions)
231         {
232             if (name.endsWith(extension))
233             {
234                 return true;
235             }
236         }
237         return false;
238     }
239 }
240 // 创建一个ImageHolder类,用于封装图片名、图片ID
241 class ImageHolder
242 {
243     // 封装图片的ID
244     private int id;
245     // 封装图片的图片名字
246     private String name;
247     public ImageHolder(){}
248     public ImageHolder(int id , String name)
249     {
250         this.id = id;
251         this.name = name;
252     }
253     // id的setter和getter方法
254     public void setId(int id)
255     {
256         this.id = id;
257     }
258     public int getId()
259     {
260         return this.id;
261     }
262     // name的setter和getter方法
263     public void setName(String name)
264     {
265         this.name = name;
266     }
267     public String getName()
268     {
269         return this.name;
270     }
271     // 重写toString方法,返回图片名
272     public String toString()
273     {
274         return name;
275     }
276 }

View Code

图片 46

  使用ResultSetMetaData分析结果集:

    当执行SQL查询后得以透过运动记录指针来遍历ResultSet的每条记下,但先后可能不明了该ResultSet里带有如何数据列,以及种种数据列的数据类型,那么可以通

     过ResultSetMetaData来博取有关ResultSet的叙说音信:

    MetaData的意趣是元数据,即描述其余数据的多少,因而ResultSetMetaData封装了描述ResultSet对象的多寡;后边还要介绍的DatabaseMetaData则封装了描述

     Database的数据。

    ResultSet中涵盖了1个getMetaData()方法,该方法能够重返该ResultSet对应的ResultSetMetaData对象。一旦获得了ResultSetMetaData对象就足以透过

     ResultSetMetaData提供的豁达艺术来回到ResultSet的讲述音讯。常用艺术有如下多少个:

      1.int getColumnCount():再次来到该ResultSet的列数量

      2.String getColumnName(int Column):再次来到内定索引的列名

      3.int getColumnType(int column):重返钦点索引的列类型

上面是二个简约的查询器,当用户在文本框内输入合法的查询语句并进行成功后,下边表格将会显得查询结果:

图片 47图片 48

  1 import java.awt.*;
  2 import java.awt.event.*;
  3 import javax.swing.*;
  4 import javax.swing.table.*;
  5 import java.util.*;
  6 import java.io.*;
  7 import java.sql.*;
  8 
  9 public class QueryExecutor
 10 {
 11     JFrame jf = new JFrame("查询执行器");
 12     private JScrollPane scrollPane;
 13     private JButton execBn = new JButton("查询");
 14     // 用于输入查询语句的文本框
 15     private JTextField sqlField = new JTextField(45);
 16     private static Connection conn;
 17     private static Statement stmt;
 18     // 采用静态初始化块来初始化Connection、Statement对象
 19     static
 20     {
 21         try
 22         {
 23             Properties props = new Properties();
 24             props.load(new FileInputStream("mysql.ini"));
 25             String drivers = props.getProperty("driver");
 26             String url = props.getProperty("url");
 27             String username = props.getProperty("user");
 28             String password = props.getProperty("pass");
 29             // 加载数据库驱动
 30             Class.forName(drivers);
 31             // 取得数据库连接
 32             conn = DriverManager.getConnection(url, username, password);
 33             stmt = conn.createStatement();
 34         }
 35         catch (Exception e)
 36         {
 37             e.printStackTrace();
 38         }
 39     }
 40     // --------初始化界面的方法---------
 41     public void init()
 42     {
 43         JPanel top = new JPanel();
 44         top.add(new JLabel("输入查询语句:"));
 45         top.add(sqlField);
 46         top.add(execBn);
 47         // 为执行按钮、单行文本框添加事件监听器
 48         execBn.addActionListener(new ExceListener());
 49         sqlField.addActionListener(new ExceListener());
 50         jf.add(top , BorderLayout.NORTH);
 51         jf.setSize(680, 480);
 52         jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 53         jf.setVisible(true);
 54     }
 55     // 定义监听器
 56     class ExceListener implements ActionListener
 57     {
 58         public void actionPerformed(ActionEvent evt)
 59         {
 60             // 删除原来的JTable(JTable使用scrollPane来包装)
 61             if (scrollPane != null)
 62             {
 63                 jf.remove(scrollPane);
 64             }
 65             try(
 66                 // 根据用户输入的SQL执行查询
 67                 ResultSet rs = stmt.executeQuery(sqlField.getText()))
 68             {
 69                 // 取出ResultSet的MetaData
 70                 ResultSetMetaData rsmd = rs.getMetaData();
 71                 Vector<String> columnNames =  new Vector<>();
 72                 Vector<Vector<String>> data = new Vector<>();
 73                 // 把ResultSet的所有列名添加到Vector里
 74                 for (int i = 0 ; i < rsmd.getColumnCount(); i++ )
 75                 {
 76                     columnNames.add(rsmd.getColumnName(i + 1));
 77                 }
 78                 // 把ResultSet的所有记录添加到Vector里
 79                 while (rs.next())
 80                 {
 81                     Vector<String> v = new Vector<>();
 82                     for (int i = 0 ; i < rsmd.getColumnCount(); i++ )
 83                     {
 84                         v.add(rs.getString(i + 1));
 85                     }
 86                     data.add(v);
 87                 }
 88                 // 创建新的JTable
 89                 JTable table = new JTable(data , columnNames);
 90                 scrollPane = new JScrollPane(table);
 91                 // 添加新的Table
 92                 jf.add(scrollPane);
 93                 // 更新主窗口
 94                 jf.validate();
 95             }
 96             catch (Exception e)
 97             {
 98                 e.printStackTrace();
 99             }
100         }
101     }
102     public static void main(String[] args)
103     {
104         new QueryExecutor().init();
105     }
106 }

View Code

图片 49

    即使ResultSetMetaData能够确切的剖析出ResultSet里带有多少列,以及每列的列名、数据类型等,但运用ResultSetMetaData须要一定的系统开发,由此若在编制程序进程中

     已经理解ResultSet里富含多少列,以及每列的列名、类型等音讯,就从未要求运用ResultSetMetaData来分析该ResultSet对象了。

  Java7的RowSet1.1:

    RowSet接口再三再四了ResultSet接口,RowSet接口下富含JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet和WebRowSet常用子接口。除了JdbcRowSet须求保

     持与数据库连接之外,别的五个子接口都是离线的RowSet,无需保持与数据库的连接。

    与ResultSet相比较,RowSet暗中同意是可滚动、可更新、可体系化的结果集,而且作为JavaBean使用,由此能造福地在网络上传输,用于共同两端的数据。对于离线RowSet而

     言,程序在开创RowSet时已经把多少从底层数据库读取到内部存款和储蓄器,由此得以足够利用总计机内存,从而下落数据库服务器的载荷,进步程序质量。

  Java7新增的RowSetFactory与RowSet:

    Java7新增了RowSetProvider类和RowSetFactory接口,其中RowSetProvider负责创造RowSetFactory,而RowSetFactory则提供了如下方法来创建RowSet实例:

      1.CachedRowSet
createCachedRowSet():创造一个暗许的CachedRowSet。

      2.FilteredRowSet
createFilteredRowSet():创造二个暗中认可的FilteredRowSet。

      3.JdbcRowSet createJdbcRowSet():创立3个暗许的JdbcRowSet。

      4.JoinRowSet createJoinRowSet():成立3个暗中同意的JoinRowSet。

      5.WebRowSet createWebRowSet():创制多少个暗中认可的WebRowSet。

    通过使用RowSetFactory,就足以把应用程序与RowSet实现类分离开,有利于后期的升级、扩大。

上面采取RowSetFactory来成立JdbcRowSet实例:

图片 50图片 51

 1 import java.util.*;
 2 import java.io.*;
 3 import java.sql.*;
 4 import javax.sql.rowset.*;
 5 
 6 public class RowSetFactoryTest
 7 {
 8     private String driver;
 9     private String url;
10     private String user;
11     private String pass;
12     public void initParam(String paramFile)throws Exception
13     {
14         // 使用Properties类来加载属性文件
15         Properties props = new Properties();
16         props.load(new FileInputStream(paramFile));
17         driver = props.getProperty("driver");
18         url = props.getProperty("url");
19         user = props.getProperty("user");
20         pass = props.getProperty("pass");
21     }
22 
23     public void update(String sql)throws Exception
24     {
25         // 加载驱动
26         Class.forName(driver);
27         // 使用RowSetProvider创建RowSetFactory
28         RowSetFactory factory = RowSetProvider.newFactory();
29         try(
30             // 使用RowSetFactory创建默认的JdbcRowSet实例
31             JdbcRowSet jdbcRs = factory.createJdbcRowSet())
32         {
33             // 设置必要的连接信息
34             jdbcRs.setUrl(url);
35             jdbcRs.setUsername(user);
36             jdbcRs.setPassword(pass);
37             // 设置SQL查询语句
38             jdbcRs.setCommand(sql);
39             // 执行查询
40             jdbcRs.execute();
41             jdbcRs.afterLast();
42             // 向前滚动结果集
43             while (jdbcRs.previous())
44             {
45                 System.out.println(jdbcRs.getString(1)
46                     + "\t" + jdbcRs.getString(2)
47                     + "\t" + jdbcRs.getString(3));
48                 if (jdbcRs.getInt("student_id") == 3)
49                 {
50                     // 修改指定记录行
51                     jdbcRs.updateString("student_name", "孙悟空");
52                     jdbcRs.updateRow();
53                 }
54             }
55         }
56     }
57     public static void main(String[] args)throws Exception
58     {
59         RowSetFactoryTest jt = new RowSetFactoryTest();
60         jt.initParam("mysql.ini");
61         jt.update("select * from student_table");
62     }
63 }

View Code

图片 52

地方程序接纳RowSetFactory来创立JdbcRowSet对象。由于通过那种方法开创的JdbcRowSet还不曾传来Connection参数,因而先后还需调用setUrl()、setUsername()、setPassword()等措施来安装数据库连接新闻。

  离线RowSet:

    在应用ResultSet的时日,程序查询获得ResultSet之后必须及时读取或处理它对应的记录,不然一旦关闭Connection,再经过ResultSet读取记录就会抓住那三个。

    离线RowSet会直接将底层数据读入内部存款和储蓄器中,封装成RowSet对象,而RowSet对象则一心能够当成JavaBean来使用,由此不但安全,且编制程序十一分不难易行。CachedRowSet是

     全部离线RowSet的父接口。

上面以CachedRowSet为例举行介绍:

图片 53图片 54

 1 import java.util.*;
 2 import java.io.*;
 3 import java.sql.*;
 4 import javax.sql.*;
 5 import javax.sql.rowset.*;
 6 
 7 public class CachedRowSetTest
 8 {
 9     private static String driver;
10     private static String url;
11     private static String user;
12     private static String pass;
13     public void initParam(String paramFile)throws Exception
14     {
15         // 使用Properties类来加载属性文件
16         Properties props = new Properties();
17         props.load(new FileInputStream(paramFile));
18         driver = props.getProperty("driver");
19         url = props.getProperty("url");
20         user = props.getProperty("user");
21         pass = props.getProperty("pass");
22     }
23 
24     public CachedRowSet query(String sql)throws Exception
25     {
26         // 加载驱动
27         Class.forName(driver);
28         // 获取数据库连接
29         Connection conn = DriverManager.getConnection(url , user , pass);
30         Statement stmt = conn.createStatement();
31         ResultSet rs = stmt.executeQuery(sql);
32         // 使用RowSetProvider创建RowSetFactory
33         RowSetFactory factory = RowSetProvider.newFactory();
34         // 创建默认的CachedRowSet实例
35         CachedRowSet cachedRs = factory.createCachedRowSet();
36         // 使用ResultSet装填RowSet
37         cachedRs.populate(rs);    // ①
38         // 关闭资源
39         rs.close();
40         stmt.close();
41         conn.close();
42         return cachedRs;
43     }
44     public static void main(String[] args)throws Exception
45     {
46         CachedRowSetTest ct = new CachedRowSetTest();
47         ct.initParam("mysql.ini");
48         CachedRowSet rs = ct.query("select * from student_table");
49         rs.afterLast();
50         // 向前滚动结果集
51         while (rs.previous())
52         {
53             System.out.println(rs.getString(1)
54                 + "\t" + rs.getString(2)
55                 + "\t" + rs.getString(3));
56             if (rs.getInt("student_id") == 3)
57             {
58                 // 修改指定记录行
59                 rs.updateString("student_name", "孙悟空");
60                 rs.updateRow();
61             }
62         }
63         // 重新获取数据库连接
64         Connection conn = DriverManager.getConnection(url
65             , user , pass);
66         conn.setAutoCommit(false);
67         // 把对RowSet所做的修改同步到底层数据库
68         rs.acceptChanges(conn);
69     }
70 }

View Code

图片 55

  从地方程序能够看看在Connection关闭的景色下,程序照旧得以读取、修改RowSet里的笔录。为了将先后对离线RowSet所做的修改同步到底层数据库,程序在调用RowSet的

   acceptChanges()方法时,必须传入Connection。

  离线RowSet的询问分页:

    由于CachedRowSet会将数据记录第③手装载到内部存储器中,若SQL查询再次来到的笔录过大,CachedRowSet将会占有多量内部存款和储蓄器,在好几极端气象下,将会造成内部存款和储蓄器溢出。

    未缓解上述难题,CachedRowSet提供了分页功效。即二遍只装载ResultSet里的某几条记下,那样就防止了CachedRowSet占用内部存款和储蓄器过大的标题。

    CachedRowSet提供了之类方法控制分页:

      1.populate(ResultSet rs, int
startRow):使用给定的ResultSet装填RowSet,从ResultSet的第startRow条记录开头装填

      2.setPageSize(int
pageSize):设置CachedRowSet每一趟回到多少条记下

      3.previousPage():在底层ResultSet可用的动静下,让CachedRowSet读取上一页记录。

      4.nextPage():在尾部ResultSet可用的景况下,让CachedRowSet读取下一页记录

下边程序示范了CachedRowSet的分页协理:

图片 56图片 57

 1 import java.util.*;
 2 import java.io.*;
 3 import java.sql.*;
 4 import javax.sql.*;
 5 import javax.sql.rowset.*;
 6 
 7 public class CachedRowSetPage
 8 {
 9     private String driver;
10     private String url;
11     private String user;
12     private String pass;
13     public void initParam(String paramFile)throws Exception
14     {
15         // 使用Properties类来加载属性文件
16         Properties props = new Properties();
17         props.load(new FileInputStream(paramFile));
18         driver = props.getProperty("driver");
19         url = props.getProperty("url");
20         user = props.getProperty("user");
21         pass = props.getProperty("pass");
22     }
23 
24     public CachedRowSet query(String sql , int pageSize
25         , int page)throws Exception
26     {
27         // 加载驱动
28         Class.forName(driver);
29         try(
30             // 获取数据库连接
31             Connection conn = DriverManager.getConnection(url , user , pass);
32             Statement stmt = conn.createStatement();
33             ResultSet rs = stmt.executeQuery(sql))
34         {
35             // 使用RowSetProvider创建RowSetFactory
36             RowSetFactory factory = RowSetProvider.newFactory();
37             // 创建默认的CachedRowSet实例
38             CachedRowSet cachedRs = factory.createCachedRowSet();
39             // 设置每页显示pageSize条记录
40             cachedRs.setPageSize(pageSize);
41             // 使用ResultSet装填RowSet,设置从第几条记录开始
42             cachedRs.populate(rs , (page - 1) * pageSize + 1);
43             return cachedRs;
44         }
45     }
46     public static void main(String[] args)throws Exception
47     {
48         CachedRowSetPage cp = new CachedRowSetPage();
49         cp.initParam("mysql.ini");
50         CachedRowSet rs = cp.query("select * from student_table" , 3 , 2);   // ①
51         // 向后滚动结果集
52         while (rs.next())
53         {
54             System.out.println(rs.getString(1)
55                 + "\t" + rs.getString(2)
56                 + "\t" + rs.getString(3));
57         }
58     }
59 }

View Code

图片 58

次第中要询问第②页的笔录,每页突显3条记下。

事务处理:

  事物的概念和MySQL事务补助:

    事务是由一步或几步数据库操作类别组成的逻辑执行单元,那连串操作还是全体推行,要么全体放任进行。

    事务有着多少个特点:原子性(Atomicity)、一致性(Consistency)、隔绝性(Isolation)和连绵(Durability)。简称ACID天性。

  JDBC的政工帮助:

    JDBC连接的事务支持有Connection提供,Connection默许打开自动提交,即关闭工作。那种气象下,每一条SQL语句一旦实施,便会立马付给到数据库,永久生效,不能够

     对其展开回滚操作。

    可调用Connection的setAutoCommit()方法来关闭自动提交,开启事务:

    //conn.setAutoCommit(false);

    等到持有SQL语句都被实施,程序可以调用Connection的commit()方法来交给业务:

    //conn.commit();

    若任意一条SQL语句执行破产,则应当用Connection的rollback()方法来回滚事务:

    //conn.rollback();

    实际上,当Connection遭受1个未处理的SQLException至极时,系统将会难堪退出,事务也会自动回滚。但若程序捕获了该尤其,则须求在相当处理块中显式地回滚

     事务

图片 59图片 60

 1 import java.sql.*;
 2 import java.io.*;
 3 import java.util.*;
 4 
 5 public class TransactionTest
 6 {
 7     private String driver;
 8     private String url;
 9     private String user;
10     private String pass;
11     public void initParam(String paramFile)throws Exception
12     {
13         // 使用Properties类来加载属性文件
14         Properties props = new Properties();
15         props.load(new FileInputStream(paramFile));
16         driver = props.getProperty("driver");
17         url = props.getProperty("url");
18         user = props.getProperty("user");
19         pass = props.getProperty("pass");
20     }
21     public void insertInTransaction(String[] sqls) throws Exception
22     {
23         // 加载驱动
24         Class.forName(driver);
25         try(
26             Connection conn = DriverManager.getConnection(url , user , pass))
27         {
28             // 关闭自动提交,开启事务
29             conn.setAutoCommit(false);
30             try(
31                 // 使用Connection来创建一个Statment对象
32                 Statement stmt = conn.createStatement())
33             {
34                 // 循环多次执行SQL语句
35                 for (String sql : sqls)
36                 {
37                     stmt.executeUpdate(sql);
38                 }
39             }
40             // 提交事务
41             conn.commit();
42         }
43     }
44     public static void main(String[] args) throws Exception
45     {
46         TransactionTest tt = new TransactionTest();
47         tt.initParam("mysql.ini");
48         String[] sqls = new String[]{
49             "insert into student_table values(null , 'aaa' ,1)",
50             "insert into student_table values(null , 'bbb' ,1)",
51             "insert into student_table values(null , 'ccc' ,1)",
52             // 下面这条SQL语句将会违反外键约束,
53             // 因为teacher_table中没有ID为5的记录。
54             "insert into student_table values(null , 'ccc' ,5)" //①
55         };
56         tt.insertInTransaction(sqls);
57     }
58 }

View Code

图片 61

 下面代码报错会因为插入语句第⑥条有错。正是因为那条语句出错,导致爆发至极,且该特别没有得随地理,引起程序非符合规律甘休,所以工作自动回滚,下面3条插入语句无效。

    Connection也提供了设置中间点的法子:

      1.Savepoint
setSavepoint():在日前政工中创立2个未命名的中间点,并再次来到代表该中间点的Savepoint对象

      2.Savepoint setSavepoint(String
name):在此时此刻事情中创设一个持有内定名称的中间点,并再次回到代表该中间点的SavepointSavepoint对象。

    平常来说设置中间点时,没有要求钦点名称,因为Connection回滚到内定中间点时,并不是依据名字回滚的,而是基于中间点对象回滚的,Connection提供了

     rollback(Savepoint savepoint)方法回滚到钦赐中间点。

  Java8升高的批量翻新:

    JDBC还提供了贰个批量翻新的遵守,批量立异时,多条SQL语句将被当作一批操作被同时采集,并同时提交。

    批量革新必须获得底层数据库的支撑,能够通过调用DatabaseMetaData的supportsBatchUpdates()方法来查阅底层数据库是不是帮衬批量翻新。

    使用批量立异必要先创立多少个Statement对象,然后使用该指标的addBatch()方法将多条SQL语句同时采集,最终调用Java6人Statement对象新增的executeLargeBatch()或

     原有的executeBatch()方法同时推行那么些SQL语句。只要批量操作中任何一条SQL语句影响的笔录条数可能超越Integer.MAX_VALUE,就相应使用executeLargeBatch()方

     法。如下:

图片 62图片 63

1 Statement stmt = conn.createStatement();
2 //使用Statement同时收集多条SQL语句
3 stmt.addBatch(sql1);
4 stmt.addBatch(sql2);
5 stmt.addBatch(sql3);
6 ...
7 //同时执行所有的SQL语句
8 stmt.executeLargeBatch();

View Code

    若在批量翻新的addBatch()方法中添加了select查询语句,程序将会直接现身错误。为了让批量操作能够正确的处理错误,必须把批量推行的操作视为单个事务,若批量更

     新在推行进度中退步,则让工作回滚到批量操作起来从前的图景。为完结那种意义,程序应该在起来批量操作从前先关闭自动提交,然后初阶征集更新语句,当批量操作

     执行实现后,提交业务,并恢复生机以前的机动提交格局,如下:

图片 64图片 65

 1 //保存当前的自动的提交模式
 2 boolean autoCommit = conn.getAutoCommit();
 3 //关闭自动提交
 4 conn.setAutoCommit(false);
 5 Statement stmt = conn.createStatement();
 6 //使用Statement同时收集多条SQL语句
 7 stmt.addBatch(sql1);
 8 stmt.addBatch(sql2);
 9 stmt.addBatch(sql3);
10 ...
11 //同时执行所有的SQL语句
12 stmt.executeLargeBatch();
13 //提交修改
14 conn.commit();
15 //恢复原有的紫东提交模式
16 conn.setAutocommit(autoCommit);

View Code

    MySQL的最新驱动照旧不支持executeLargeBatch()方法,对于数据库驱动不援助executeLargeBatch()的景色,则不得不依旧选取古板的executeBatch()方法。

浅析数据库新闻:

  使用DatabaseMetaData分析数据库新闻:

    JDBC提供了DatabaseMetaData来封装数据库连接对应数据库的新闻,通过Connection提供的getMetaData()方法就足以拿走数据库对应的DatabaseMetaData对象

    DatabaseMetaData接口平日由驱动程序供应商提供达成,其目标是让用户领悟底层数据库的连带音讯。使用该接口的指标是意识怎么处理底层数据库,尤其是对此准备与

     多少个数据库一起利用的应用程序——因为应用程序须求在多少个数据库之间切换,所以必须运用该接口来找出底层数据库的功能,如:调用supportsCorrelatedSubqueries

     ()方法查看是还是不是能够动用关联子查询,可能调用supportsBatchUpdates()方法查看是或不是能够使用批量立异。

    许多DatabaseMetaData方法以ResultSet对象的样式再次回到查询消息,然后选取ResultSet的正规格局(如:getString()和getInt())即可从那些ResultSet对象中获取数据。若

     查询的新闻不可用,则将再次来到三个空ResultSet对象。

    DatabaseMetaData的洋洋办法都亟需传入一个XXXPattern情势字符串,那里的XXX帕特tern不是正则表明式,而是SQL里的情势字符串,即用%意味着私行三个字符,使用下

     划线代表2个字符。在一般情形下,若把该方式字符串的参数值设置为null,即声明该参数不作为过滤条件。

    上面程序通过DatabaseMetaData分析了当前Connection连接对应数据库的一些基本消息,包蕴近来数据库包含多少数据表,存款和储蓄进程,student_table表的数据列、主键、

     外键等新闻:

图片 66图片 67

 1 import java.sql.*;
 2 import java.util.*;
 3 import java.io.*;
 4 
 5 public class DatabaseMetaDataTest
 6 {
 7     private String driver;
 8     private String url;
 9     private String user;
10     private String pass;
11     public void initParam(String paramFile)throws Exception
12     {
13         // 使用Properties类来加载属性文件
14         Properties props = new Properties();
15         props.load(new FileInputStream(paramFile));
16         driver = props.getProperty("driver");
17         url = props.getProperty("url");
18         user = props.getProperty("user");
19         pass = props.getProperty("pass");
20     }
21     public void info() throws Exception
22     {
23         // 加载驱动
24         Class.forName(driver);
25         try(
26             // 获取数据库连接
27             Connection conn = DriverManager.getConnection(url
28                 , user , pass))
29         {
30             // 获取的DatabaseMetaData对象
31             DatabaseMetaData dbmd = conn.getMetaData();
32             // 获取MySQL支持的所有表类型
33             ResultSet rs = dbmd.getTableTypes();
34             System.out.println("--MySQL支持的表类型信息--");
35             printResultSet(rs);
36             // 获取当前数据库的全部数据表
37             rs = dbmd.getTables(null,null, "%" , new String[]{"TABLE"});
38             System.out.println("--当前数据库里的数据表信息--");
39             printResultSet(rs);
40             // 获取student_table表的主键
41             rs = dbmd.getPrimaryKeys(null , null, "student_table");
42             System.out.println("--student_table表的主键信息--");
43             printResultSet(rs);
44             // 获取当前数据库的全部存储过程
45             rs = dbmd.getProcedures(null , null, "%");
46             System.out.println("--当前数据库里的存储过程信息--");
47             printResultSet(rs);
48             // 获取teacher_table表和student_table之间的外键约束
49             rs = dbmd.getCrossReference(null,null, "teacher_table"
50                 , null, null, "student_table");
51             System.out.println("--teacher_table表和student_table之间"
52                 + "的外键约束--");
53             printResultSet(rs);
54             // 获取student_table表的全部数据列
55             rs = dbmd.getColumns(null, null, "student_table", "%");
56             System.out.println("--student_table表的全部数据列--");
57             printResultSet(rs);
58         }
59     }
60     public void printResultSet(ResultSet rs)throws SQLException
61     {
62         ResultSetMetaData rsmd = rs.getMetaData();
63         // 打印ResultSet的所有列标题
64         for (int i = 0 ; i < rsmd.getColumnCount() ; i++ )
65         {
66             System.out.print(rsmd.getColumnName(i + 1) + "\t");
67         }
68         System.out.print("\n");
69         // 打印ResultSet里的全部数据
70         while (rs.next())
71         {
72             for (int i = 0; i < rsmd.getColumnCount() ; i++ )
73             {
74                 System.out.print(rs.getString(i + 1) + "\t");
75             }
76             System.out.print("\n");
77         }
78         rs.close();
79     }
80     public static void main(String[] args)
81         throws Exception
82     {
83         DatabaseMetaDataTest dt = new DatabaseMetaDataTest();
84         dt.initParam("mysql.ini");
85         dt.info();
86     }
87 }

View Code

图片 68

结果太多,只截取一部分。

  使用系统表分析数据库新闻:

    除了DatabaseMetaData来分析底层数据库消息之外,若已经规定应用程序所以用的数据库系统,则足以由此数据库的体系来分析数据库新闻。

    系统表又称为数据字典,数据字典的多寡一般由数据库系统承担维护,用户平日只好查询数据字典,而无法修改数据字典的剧情。

    MySQL数据库使用information_schema数据库来保存系统表,在数据Curry含有了大气系统表,常用系统表的简易介绍如下:

      1.tables:存放数据Curry具有数据表新闻

      2.schemata:存放数据库里有着数据库的音讯

      3.views:存放数据Curry全数视图的音信

      4.columns:存放数据Curry有着列的音讯

      5.triggers:存放数据Curry全数触发器的音信

      6.routines:存放数据Curry有着存款和储蓄进程和函数的音信

      7.key_column_usage:存放数据Curry拥有拥有约束的键音信

      8.table_constraints:存放数据Curry一切约束表的音信

      9.statistics:存放数据Curry一切目录的音讯

使用连接池管理总是:

  数据库连接的建立和倒闭是极开销系统能源的操作,数据库连接池的化解方案是:当应用程序运营时,系统积极建立充分的数据库连接,并将那几个连接组成1个连接池。每一遍应

   用程序请求数据库连接是,无需另行打开连接,而是从连接池中取出已部分连年使用,使用完后不再关闭数据库连接,而是一直将再而三归还给连接池。

  对于共享能源的气象,有一个通用的设计方式:能源池(Resource
Pool),用于缓解财富的数次呼吁、释放所导致的属性降低。

  数据库连接池是Connection对象的工厂,数据库连接池的常用参数如下:

    1.数据库的起来连接数

    2.连接池的最达累斯萨Lamb接数

    3.连接池的小小连接数

    4.连接池每回扩充的体量

  JDBC的数据库连接池使用
javax.sql.DataSource来表示,DataSource只是2个接口,该接口经常由商用服务器提供完结,也有局部开源组织提供实现(如DBCP和C3P0)。

  DBCP数据源:

    DBCP是Apache软件基金组织下的开源连接完毕,该连接池注重该协会下的另多少个开源系统:common-pool。若必要采纳该连接池达成,则应在系统中扩充七个jar
文件:

      1.commons-dbcp.jar:连接池的兑现

      2.commons-pool.jar:连接池完结的重视库

    汤姆cat的连接池就是利用该连接池完毕的。数据库连接池既能够与应用服务器整合利用,也足以由应用程序独立使用。

    上面代码片段示范了使用DBCP来取得数据库连接方式:

图片 69图片 70

 1 //创建连接池实例
 2 BasicDataSource ds = new BasicDataSourc();
 3 //设置连接池所需驱动
 4 ds.setDriverClassName("com.mysql.jdbc.Driver");
 5 //设置连接数据库的URL
 6 ds.setUrl("jdbc:mysql://localhost:3306/javaee");
 7 //设置连接数据库的用户名
 8 ds.setUsername("root");
 9 //设置连接数据库的密码
10 ds.setPassword("pass");
11 //设置连接池的初始连接数
12 ds.setInitialSize(5);
13 //设置连接池最多可有多少个活动连接数
14 ds.setMaxActive(20);
15 //设置连接池中最少有2个空闲的连接
16 ds.setMinIdle(2);

View Code

    数据源和数据库连接不相同,数据源无需创立四个,它是发生数据库连接的工厂,由此整个应用只需求二个数据源即可。即:三个用到,下面代码只必要实践一回即可。

    提议把上面程序中的ds设置成static成员变量,并且在采纳起来时立时初步化数据源对象,程序中全体须要获得数据库连接的地方平昔访问该ds对象,并获取数据库连接即

     可。

    //通过数据源获取数据库连接

    Connection conn = ds.getConnection();

    当数据库访问甘休后,程序依旧像从前一样关闭数据库连接:

    //释放数据库连接

    conn.close();

  C3P0数据源:

    C3P0数据源品质更胜一筹,Hibernate就引进应用该连接池。C3P0连接池不仅能够自动清理不在使用的Connection,还足以活动清理ResultSet和Statement。

    若需求动用C3P0连接池,则应在系统中加进如下JAENVISION文件

      1.c3p0-0.9.1.2.jar:C3P0连接池的达成

    下边代码通过C3P0连接池得到数据库连接:

图片 71图片 72

 1 //创建连接池实例
 2 ComboPooledDataSource ds = new ComboPooledDataSource();
 3 //设置连接池所需驱动
 4 ds.setDriverClass("com.mysql.jdbc.Driver");
 5 //设置连接数据库的URL
 6 ds.setJdbcUrl("jdbc:mysql://localhost:3306/javaee");
 7 //设置连接数据库的用户名
 8 ds.setUser("root");
 9 //设置连接数据库的密码
10 ds.setPassword("pass");
11 //设置连接池的最大连接数
12 ds.setMaxPoolSize(40);
13 //设置连接池的最小连接数
14 ds.setMinPoolSIze(2);
15 //设置连接池的初始连接数
16 ds.setInitialPoolSize(10);
17 //设置连接池的缓存Statement的最大数
18 ds.setMaxStatements(180);

View Code

    通过如下代码获取数据库连接:

    Connection conn = ds.getConnection();