简介
注意:下面介绍的Statement都是以MySQL官方提供的驱动为实现参考
在默认情况下,同一时间每个 Statement 对象在只能打开一个 ResultSet 对象。因此,如果读取一个ResultSet对象与读取另一个交叉,则这两个对象必须是由不同的Statement 对象生成的。如果存在某个语句的打开的当前ResultSet对象,则Statement接口中的所有执行方法都会隐式关闭它。
Statement 对象用于将 SQL 语句发送到数据库执行。实际上有三种最常用的的Statement 实现类,它们分别是StatementImpl,PreparedStatement,CallableStatement,它们都作为在给定连接上执行SQL语句的包容器,用于发送特定类型的SQL语句。
StatementImpl:用于执行不带参数的简单 SQL 语句 PreparedStatement:用于执行带或不带 IN 参数的预编译 SQL CallableStatement:用于执行对数据库已存在的存储过程的调用
Statement 接口提供了执行语句和获取结果的基本方法。PreparedStatement 接口添加了处理 IN 参数的方法;而 CallableStatement 添加了处理 OUT 参数的方法。
下图是MySQL官方的驱动中的类层次结构(红色框部分):
下面就简单介绍一下Statement的使用。
StatementImpl使用
一般我们使用MySQL的驱动使用: statement = connection.createStatement();语句默认创建的就是StatementImpl。
public java.sql.Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
checkClosed();
StatementImpl stmt = new StatementImpl(getMultiHostSafeProxy(), this.database);
stmt.setResultSetType(resultSetType);
stmt.setResultSetConcurrency(resultSetConcurrency);
return stmt;
}
StatementImpl主要执行静态的SQL语句。
@Test
public void testBaseStatement() {
try {
statement = connection.createStatement();
} catch (SQLException e) {
e.printStackTrace();
}
try {
rs = statement.executeQuery("select * from user");
} catch (SQLException e) {
e.printStackTrace();
}
try {
while (rs.next()) {
int id = rs.getInt(1);
System.out.println(id);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
StatementImpl还有一个用的比较多的batch操作
@Test
public void testBatchStatement() {
String sql = null;
try {
statement = connection.createStatement();
} catch (SQLException e) {
e.printStackTrace();
}
try {
for (int i = 0; i < 20; i++) {
sql = "insert into user(name,password) values('name_" + i + "'," + "'8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c9" + i%10 + "')";
statement.addBatch(sql);
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
statement.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
}
}
PreparedStatement
PreparedStatement一般是通过connection.prepareStatement(String SQL)这样的方式实现,主要以预编译的方式处理SQL,因为预编译是在服务器端,所以不同的数据库对于PreparedStatement影响是很大的。
PreparedStatement预处理方式:
@Test
public void testPrepareStatement(){
PreparedStatement ps;
try {
ps = connection.prepareStatement("UPDATE user SET name = ? WHERE ID =?");
ps.setString(1, "ps");
ps.setInt(2, 1);
ps. executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
PreparedStatement也有一个batch模式的操作:
@Test
public void testBatchPrepareStatement(){
PreparedStatement ps;
try {
ps = connection.prepareStatement("UPDATE user SET name = ? WHERE ID =?");
for(int i =0;i<5;i++){
ps.setString(1, "psb"+i);
ps.setInt(2, i+1);
ps.addBatch();
}
ps.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
}
}
CallableStatement
CallableStatement主要是用来处理存储过程的,因为存储过程的诸多问题,如可移植性差,业务耦合严重,更多的锁争用等。所以互联网公司基本不会使用存储过程。按照常用的高并发模式的操作经验来看就是尽量将数据结算向上移动到服务层,这样可以提高模块的可重用性,减少数据库的压力。现在扩展服务有很多支持已经比较方便了,但是数据层要扩展还是比较麻烦的事情。
不管怎么说,还是来看一下使用CallableStatement调用存储过程的例子吧:
@Test
public void testCallablePrepareStatement(){
CallableStatement cs;
try {
cs = connection.prepareCall("{call my_add(?,?,?)}");
cs.setInt(1, 5);
cs.setInt(2, 95);
cs.registerOutParameter(3, java.sql.Types.INTEGER);
cs.executeUpdate();
int result = cs.getInt(3);
System.out.println(result);
} catch (SQLException e) {
e.printStackTrace();
}
}
存储过程的代码见附录。我们使用prepareCall来获取一个CallableStatement对象。CallableStatement本身是不区分in还是out参数的。如果是in参数只需要通过setXXX设置对应的参数值就可以了。如果是out参数要先registerOutParameter注册一下,也是要对应的位置。例如上面的实例中的就第3个参数(第3个占位符?)是out参数,类型是Integer,就使用cs.registerOutParameter(3, java.sql.Types.INTEGER);然后就可以使用cs.getInt(3)获取对应的结果。
附录
创建表SQL
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL COMMENT '用户名,字母数字中文',
`password` char(64) NOT NULL COMMENT '密码,sha256加密',
`nick_name` varchar(20) DEFAULT '' COMMENT '昵称',
`portrait` varchar(30) DEFAULT '' COMMENT '头像,使用相对路径',
`status` enum('valid','invalid') DEFAULT 'valid' COMMENT 'valid有效,invalid无效',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='用户表';
存储过程
DROP PROCEDURE IF EXISTS `my_add`;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `my_add`(IN a int, IN b int, OUT sum int)
BEGIN
if a is null then set a = 0;
end if;
if b is null then set b = 0;
end if;
set sum = a + b;
END
;;
DELIMITER ;
处理一个加法,2个传入参数,1个传出参数。DEFINER的位置有2个选择一个是DEFINER表示存储过程使用的权限是定义者的权限,一个是INVOKE表示存储过程使用的权限是调用者的权限。
测试代码
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class StatementTest {
private Connection connection;
private Statement statement;
private ResultSet rs;
private final static String url = "jdbc:mysql://127.0.0.1:3306/design?useUnicode=true&characterEncoding=utf-8";
private final static String user = "tim";
private final static String password = "123456";
@Before
public void setUp() {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
connection = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Test
public void testConnection() {
Assert.assertNotNull(connection);
}
@Test
public void testBaseStatement() {
try {
statement = connection.createStatement();
} catch (SQLException e) {
e.printStackTrace();
}
try {
rs = statement.executeQuery("select * from user");
} catch (SQLException e) {
e.printStackTrace();
}
try {
while (rs.next()) {
int id = rs.getInt(1);
System.out.println(id);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
@Test
public void testBatchStatement() {
String sql = null;
try {
statement = connection.createStatement();
} catch (SQLException e) {
e.printStackTrace();
}
try {
for (int i = 0; i < 20; i++) {
sql = "insert into user(name,password) values('name_" + i + "'," + "'8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c9" + i%10 + "')";
statement.addBatch(sql);
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
statement.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Test
public void testPrepareStatement(){
PreparedStatement ps;
try {
ps = connection.prepareStatement("UPDATE user SET name = ? WHERE ID =?");
ps.setString(1, "ps");
ps.setInt(2, 1);
ps. executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Test
public void testBatchPrepareStatement(){
PreparedStatement ps;
try {
ps = connection.prepareStatement("UPDATE user SET name = ? WHERE ID =?");
for(int i =0;i<5;i++){
ps.setString(1, "psb"+i);
ps.setInt(2, i+1);
ps.addBatch();
}
ps.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Test
public void testCallablePrepareStatement(){
CallableStatement cs;
try {
cs = connection.prepareCall("{call my_add(?,?,?)}");
cs.setInt(1, 5);
cs.setInt(2, 95);
cs.registerOutParameter(3, java.sql.Types.INTEGER);
cs.executeUpdate();
int result = cs.getInt(3);
System.out.println(result);
} catch (SQLException e) {
e.printStackTrace();
}
}
}