JDBC数据库开发入门
JDBC入门
步骤:
- 导jar包:驱动!
- 加载驱动类:Class.forName(“类名”);
- 给出url,username,password,其中url记下来
- 使用DriverManager类来得到Connection对象
1.什么是JDBC
JDBC(java DataBase Connectivity)就是Java数据库连接,即用Java语言来操作数据库
2.JDBC原理
JDBC是由SUN公司提供的,一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN公司的规范提供一套访问自己公司的数据库服务器的API出现。
SUN公司提供的命名规范名为JDBC,各个厂商提供的,遵循了JDBC规范的,可以访问自己数据库的API被称为驱动。
3.JDBC核心类(接口)介绍
JDBC中的核心类有:DriverManager,Connnection,Statement和ResultSet
DriverManager(驱动管理器)的作用有两个:
- 注册驱动:这可以让JDBC知道要使用的是哪个驱动
- 获取Connection:如果可以获取到Connection,那么说明已经与数据库连接上了
Connection对象表示连接,与数据库的通讯都是通过这个对象展开的:
- Connection最为重要的一个方法就是用来获取Statement对象
Statement是用来向数据库发送SQL语句的,这样数据库就会执行发送过来的SQL语句:
- void executeUpdate(String sql):执行更新操作(insert,update,delete等)
- ResultSet executeQuery(String sql):执行查询操作,数据库在执行查询后会把查询结果ResultSet返回
ResultSet对象表示查询结果集,只有在执行查询操作后才会有结果集的产生
- 结果集是一个二维的表格,有行有列。
- 操作结果集要利用ResultSet内部的“行光标”,以及获取当前行上的每一列上的数据
- boolean next():是行光标移动到下一行,并返回移动后的行是否存在
- XXX getXXX(int col):获取当前行指定 列上的值
4.Hello JDBC
4.1 mysql数据库的驱动
/**
* 所有的java.sql.Driver实现类,都提供了static块,
* 块内的代码就是把自己注册到DriverManager中
*/
Class.forName("com.mysql.jdbc.Driver"); //加载驱动类(注册驱动)
/*等价于*/
com.mysql.jdbc.Driver driver = new com.mysql.jdbc.Driver(); //注册驱动
DriverManager.registerDriver(driver);
- jbdc4.0之后,每个驱动jar包中,META-INF目录下提供了一个名为java.sql.Driver的文件
- 文件的内容就是该接口的实现类名称
- 可以不用自己加载驱动类
- 但为了兼容,要写
4.2 获取连接
//jdbc协议的格式 jdbc:厂商的名称:子协议(由厂商自己规定)(http协议的格式 http://主机:端口/页面路径)
//对mysql而言,它的子协议结构://主机:端口号/数据库名称
String url = "jdbc:mysql://localhost:3306/test";
String username = "root";
String password = "";
//使用url,username,password,得到连接对象
Connection connection = DriverManager.getConnection(url,username,password);
4.3 获取Statement
//通过Connection对象创建Statement
//Statement语句->发送器,它的功能就是向数据库发送SQL语句
Statement statement = connection.createStatement();
4.4 发送SQL增、删、改
//使用Statement发送sql语句
String sql = "INSERT INTO users VALUES('ITCAST_0003','wangwu',38,'male')";
//引号内只能有一条sql语句,且不加分号
String sql = "UPDATE users SET name='zhaoliu',age=22,gender='female' WHERE
number='ITCAST_0003'";
String sql = "DELETE FROM users";
int r = statement.executeUpdate(sql); //该函数的返回值操作行数
System.out.println(r);
4.5 发送SQL查询语句
//调用Statement的ResultSet rs = execuQuery(String querySql)
ResultSet resultSet = statement.executeQuery
("select * from users");
System.out.println(resultSet); //显示未解析的结果集
4.6 读取结果集中的数据
//把行光标移动到第一行,可以使用next()方法完成
while(resultSet.next()) //把光标向下移动一行,并判断下一行是否存在
{
String number = resultSet.getString(1);
//通过列编号来获取该列的值
String name = resultSet.getString("name");
//通过列名称来获取该列的值
int age = resultSet.getInt(3);
String gender = resultSet.getString(4);
System.out.println(number + "," + name + "," + age
+ "," + gender);
}
4.7 关闭
// 倒关(先得到的资源后关闭,后得到的资源先关闭)
resultSet.close();
statement.close();
connection.close(); //这个必须要关
4.8 规范化代码
规范化代码就是无论是否出现异常,都要关闭ResultSet,Statement,以及Connection
在try外给出引用的定义,在try内为对象实例化,在finally中进行关闭
public static void fun()
throws ClassNotFoundException, SQLException
{
Connection connection = null; //定义引用
Statement statement = null;
ResultSet resultSet = null;;
try
{
String driverClassName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/px190731";
String username = "root";
String password = "";
Class.forName(driverClassName);
connection = DriverManager.getConnection(url,username,password); //实例化
statement = connection.createStatement();
String sql = "select * from users";
resultSet = statement.executeQuery(sql);
while (resultSet.next())
{
System.out.println(resultSet.getString(1) + "," +
resultSet.getString(2) + resultSet.getInt(3)
+ "," + resultSet.getString(4));
}
}
catch (Exception e)
{
throw new RuntimeException(e);
}
finally
{
//关闭
if(resultSet != null)
{
resultSet.close();
}
if(statement != null)
{
statement.close();
}
if(connection != null)
{
connection.close();
}
}
}
JDBC对象介绍
1.JDBC中的主要类(接口)
2.DriverManager
- getConnection()方法,获得Connection对象:
Class.forName(driverClassName);
Connection connection = DriverManager.getConnection(url,username,password);
注意,上面的代码可能出现两种异常:
- ClassNotFoundException:这个异常是在第一句上出现的,出现这个异常有两种可能:
- 没有给出mysql的jar包
- 把类名打错了,正确类名为:com.mysql.jdbc.Driver
- SQLException:这个异常出现在第二句,出现这个异常就是三个问题出错
- 对于DriverManager.registerDriver()方法了解即可,注册驱动一般用Class.forName()
3.Connection
Connection最重要的方法是获取Statement:
-
Statement statement = connection.createStatement();
-
Statement statement = connection.createStatement(int,int);
这两个参数是用来确定创建的Statement能生成什么样的结果集
4.Statement
Statement最为重要的方法是:
int executeUpdate(String sql);
- 执行更新操作,即insert,update,delete语句
- 也可以执行create table,alter table,drop table等语句,但很少用
- 返回值为操作行数
ResultSet executeQuery(String sql);
- 执行查询操作,即select语句
- 返回ResultSet,即结果集
booleab execute();
- 可以用来执行executeUpdate和executeQuery两个方法能执行的所有sql语句,即可以执行增删改查所有sql语句
- 该方法的返回的是一个boolean类型,表示SQL语句是否有结果集
- 如果用该方法执行更新语句,那么还要调用int getUpdateCount()来获取insert,update,delete语句所影响的行数
- 如果使用该方法执行查询语句,那么还要调用ResultSet getResult()来获取select语句的查询结果
5.ResultSet之滚动结果
ResultSet内部维护了一个行光标(游标),ResultSet提供了一些了的方法来移动游标
-
移动游标:
- 相对移动:
- boolean previous():把光标向上挪一行
- boolean next():把光标向下挪一行,返回值表示当前行是否存在
- boolean relative(int row):相对位移
- 当row为正数时,表示向下移动row行
- 为负数时,表示向上移动row行
- 绝对移动:
- void beforeFirst():把光标放到第一行前面,这也是光标的默认位置
- void afterLast():把光标放到最后一行的后面
- boolean first():把光标放到第一行的位置上,返回值表示调动光标是否成功
- boolean last():把光标方放最后一行的位置上
- boolean absolute(int row):绝对位移,把光标移动到指定的行上
- 相对移动:
-
判断游标位置:
- boolean isBeforeFirst():当前光标位置是否在第一行前面
- boolean isAfterEnd():当前光标位置是否在最后一行后面
- boolean isFirst():当前光标位置是否在第一行上
- boolean isLast():当前光标位置是否在最后一行上
-
intgetRow():返回当前光标所有行
resultSet.last(); //把光标移动到最后一行 System.out.println(resultSet.getRow()); //得到结果集的行数
6.ResultSet的特性(是否可滚动、是否敏感、是否可更新)
当使用Connection的createStatement时,已经确定了Statement生成的结果集是什么特性
- 是否可滚动
- 是否敏感,结果集数据是否能跟随数据库变化
- 是否可更新,结果集的更新是否可以影响到数据库
Statement createStatement();
生成的结果集:不滚动,不敏感,不可更新
Statement createStatement(int resultSetType,int resultSetConcurrency);
第一个参数:
- ResultSet.TYPE_FORWARD_ONLY:不滚动结果集
- ResultSet.TYPE_SCROLL_INSENSITIVE:滚动结果集,但结果集数据不会再跟随数据库而变化
- ResultSet.TYPE_SCROLL_SENSITIVE:滚动结果集,但结果集数据不会再跟随数据库而变化(没有数据库驱动支持他)
第二个参数:
- CONCUR_READ_ONLY:结果集是只读的,不能通过修改结果集而反向影响数据库
- CONCUR_UPDATABLE:结果集是可更新的,对结果集的更新可以反向影响数据库
- 结果集是否支持滚动:
- 结果集默认是不可滚动的,此时只能使用next()方法移动游标,其他方法都不可使用
- 结果集是否支持滚动,要从Connection类的createStatement()方法设置
7.ResultSet之获取列数据(获取结果集元数据)
ResultSet提供一个系列的getXxx()方法
- getInt,getString,getDouble,getDate,getTime,getObject
- 参数为列序号或者列名称
- 其中getString和getObject是万能的
- 注意,索引值是从1开始
ResultSet中对行的操作
- 得到元数据:resultSet.getMetaDate(),返回值为ResultSetMetaData
- 获取结果集列数:int getColumnCount()
- 获取指定列的列名:String getColumnName(int colIndex)
int count = resultSet.getMetaData().getColumnCount();
while(resultSet.next()) //输出结果集
{
for(int i = 1 ; i <= count ; i++)
{
System.out.print(resultSet.getObject(i));
if(i < count)
{
System.out.print(",");
}
}
System.out.println();
}
PreparedStatement
1.什么是SQL攻击
在需要用户输入的地方,用户输入的是SQL语句的片段,最终用户输入的SQL片段与我们DAO中写的SQL语句合成一个完整的SQL语句。
2.演示SQL攻击
/**
* 登录
* 使用username和password去查询数据
* 若查出结果集,说明正确,返回true
* 若查不出结果,说明用户名或密码错误,返回false
*/
public static boolean login(String username, String password)
throws ClassNotFoundException, SQLException
{
/**
* 一、得到Connection
* 二、得到Statement
* 三、得到ResultSet
* 四、rs.next()返回的是什么,我们就返回什么
*/
String driverClassName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/px190731";
String mysqlUsername = "root";
String mysqlPassword = "";
Class.forName(driverClassName);
Connection connection =
DriverManager.getConnection(url,mysqlUsername,mysqlPassword);
Statement statement = connection.createStatement();
String sql = "select * from t_user where username='" + username + "'
and password='" + password + "'";
//select * from t_user where username='a'or 'a'='a' and password='a'or 'a'='a' sql攻击
System.out.println(sql);
ResultSet resultSet = statement.executeQuery(sql);
return resultSet.next();
}
3.防止SQL攻击
不允许在可替换之处出现改变SQL语句的符号
4.PreparedStatement是什么?
- 它是Statement接口的子接口
- 强大之处:
- 防SQL攻击
- 提高代码的可读性,可维护性
- 提高效率
- 预处理的原理
- 服务器的工作:
- 校验sql语句的语法
- 编译:把sql语句变成一个与函数相似的东西
- 执行:调用函数
- PreparedStatement
- 前提:连接的数据库必须支持预处理。(现在几乎没有不支持的)
- 每个preparedStatement都与一个sql模板绑定在一起,先把sql模板给数据库,数据库先进行校验,然后再进行编译,执行时只是把参数传递过去而已
- 若第二次执行时,就不用再次校验语法,也不用再次编译,直接执行
- 服务器的工作:
5.PreparedStatement的使用
用法
- 如何得到PreparedStatement对象
- 如何得到PreparedStatement对象
- 给出SQL模板
- 调用Connection的PreparedStatement preparedStatement(String sql模板):即创建它时就让它与一条SQL模板绑定
- 调用preparedStatement的setXXX()系列方法给sql模板中的?赋值
- 调用preparedStatement的executeUpdate()或executeQuery(),但它的方法都没有参数
- 如何得到PreparedStatement对象
String sql = "select * from t_user where username=? and password=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
/**
* 二、为参数赋值
*/
preparedStatement.setString(1,username); //给第1个问号赋值,值为username
preparedStatement.setString(2,password); //给第2个问号赋值,值为password
ResultSet resultSet = preparedStatement.executeQuery();
//调用查询方法,向数据库发送查询语句
resultSet.close();
preparedStatement.clearParameters(); //再次使用时需要把原来的设置清空
preparedStatement.setString(1,username2);
preparedStatement.setString(2.password2);
resultSet = preparedStatement.executeQuery();
JdbcUtils工具类
1.JdbcUtils的作用
为了在更改连接的数据库时,不用去修改代码,编写一个JdbcUtils类,让他从配置文件中读取配置参数,然后创建连接对象
2.JdbcUtils代码
driverClassName = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/px190731
username = root
password =
public class JdbcUtils
{
private static Properties properties = null;
//只在JbdcUtils类被加载时执行一次
static
{
//给properties进行初始化,即加载bdconfig.properties文件到properties对象中
try
{
InputStream inputStream = JdbcUtils.class.getClassLoader().
getResourceAsStream("bdconfig.properties");
properties = new Properties();
properties.load(inputStream);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
//加载驱动类
try
{
Class.forName(properties.getProperty("driverClassName"));
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException
{
/**
* 1.加载配置文件
* 2.加载驱动类
* 3.调用DriverManager.getConnection()
*/
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
return DriverManager.getConnection(url,username,password);
}
}
public static void fun()
throws SQLException, IOException, ClassNotFoundException
{
Connection connection = JdbcUtils.getConnection();
}
UserDao
1.DAO模式
DAO(Data Access Object)模式就是写一个类,把访问数据库的代码封装起来。DAO在数据库与业务逻辑(Service)之间。
- 实体域,即操作的对象,例如我们操作的表时uesr表,那么就需要先写一个User类
- DAO模式需要先提供一个DAO借口
- 然后再提供一个DAO接口的实现类
- 再编写一个DAO工厂,Service通过工厂来获取DAO实现
2.代码
/**
* UserDao接口
*/
public interface UserDao
{
public void addUser(User form);
public User findByUsername(String username);
}
/**
* UserDao的某一实现类
*/
public class UserDaoImpl implements UserDao { ... }
/**
* 给出一个配置文件,文件中给出UserDao接口的实现类名称
* 通过工厂中的方法获取实现类的类型,通过反射完成创建对象
*/
cn.itcast.usesrmng.dao.UserDao:'cn.itcast.usesrmng.dao.UserDaoImpl'
/**
* 通过配置文件得到dao实现类的名称
* 通过类名称,完成创建类对象(反射完成的)
*/
public class DaoFactory
{
private static Properties props = null;
static
{
//加载配置文件的内容到props对象中
try
{
InputStream in = DaoFactory.class.getClassLoader().
getResourceAsStream("dao.properties");
props = new Properties();
props.load(in);
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
/**
* 返回一个具体UserDao的实现类对象
*/
public UserDao getUserDao()
{
//得到dao实现类的名称
String daoClassName = props.
getProperty("cn.itcast.usermng.dao.UserDao");
//通过反射来创建实现类的对象
try
{
Class class = Class.forName(daoClassName);
return (UserDao)class.newInstance();
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
}
public class UserService
{
//把具体实现类的创建,隐藏到工厂中
private UserDao userDao = DaoFactory.getUserDao();
...
}
3.修改登录注册案例,其中dao层为jdbc
public class JdbcUserDao implements UserDao
{
@Override
public void addUser(User form)
{
Connection connection = null;
PreparedStatement preparedStatement = null;
try
{
/**
* 一、得到连接
*/
connection = JdbcUtils.getConnection();
/**
* 二、准备sql模板,得到pstmt
*/
String sql = "INSERT INTO user VALUES(?,?,?,?)";
preparedStatement = connection.prepareStatement(sql);
/**
* 三、为pstmt中的问号赋值
*/
preparedStatement.setString(1,form.getUsername());
preparedStatement.setString(2,form.getPassword());
preparedStatement.setInt(3,form.getAge());
preparedStatement.setString(4,form.getGender());
/**
* 四、执行
*/
preparedStatement.executeUpdate();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
finally
{
try
{
if (preparedStatement != null) preparedStatement.close();
if (connection != null) preparedStatement.close();
}
catch (SQLException e){}
}
}
@Override
public User findByUsername(String username)
{
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try
{
connection = JdbcUtils.getConnection();
String sql = "SELECT * FROM user WHERE username=?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,username);
preparedStatement.executeQuery();
/**
* 五、把rs转换成User类型,返回
* ORM -- > 对象关系映射
*/
if(resultSet == null)
{
return null;
}
if (resultSet.next())
{
User user = new User();
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setAge(resultSet.getInt("age"));
user.setGender(resultSet.getString("gender"));
return user;
}
else
{
return null;
}
}
catch (Exception e)
{
throw new RuntimeException(e);
}
finally
{
try
{
if (preparedStatement != null) preparedStatement.close();
if (connection != null) preparedStatement.close();
if (resultSet != null) resultSet.close();
}
catch (SQLException e){}
}
}
}
时间类型
1.Java中的时间类型
-
java.sql包下给出三个与数据库相关的日期时间类型,分别是:
- Date:表示日期,只有年月日,没有时分秒,会丢失时间
- Time:表示时间,只有时分秒,没有年月日,会丢失日期
- Timestamp:表示时间戳,有年月日时分秒,以及毫秒
这三个类都是java.util.Date的子类
-
数据库类型与java中类型的对应关系
DATE -> java.sql.Date
TIME -> java.sql.Time
TIMESTAMP -> java.sql.Timestamp
2.时间类型相互转换
-
领域对象(domain)中的所有属性不能出现java.sql包下的东西
- ResultSet#getDate()返回的是java.sql.Date
- PreparedStatement#setDate(int,Date),其中第二个参数也是java.sql.Date
-
时间类型的转换:
-
java.util.Date -> java.sql.Date、Time、Timestamp
-
使用util的Date转换成毫秒值
-
使用毫秒值创建sql的Date、Time、Timestamp
-
java.util.Date date = new java.util.Date(); long l = date.getTime(); java.sql.Date sqlDate = new java.sql.Date(l);
-
-
java.sql.Date、Time、Timestamp -> java.util.Date
java.util.Date date = new java.sql.Date();
- 这一步不需要处理了,因为java.sql.Date是java.util.Date的子类
-
大数据
1.什么是大数据
所谓大数据,就是大的字节数据,或大的字符数据。
-
标准SQL中有如下的数据类型来保存大数据类型
类型 长度 tinyblob 2^8-1B(258B) blob 2^16-1B(64K) mediumblob 2^24-1B(16M) longblob 2^32-1B(4G) tinyclob 2^8-1B(258B) clob 2^16-1B(64K) mediumclob 2^24-1B(16M) longclob 2^32-1B(4G) -
但是在mysql中没有提供tinyclob,clob,mediumclob,longclob四种类型,而是使用如下四种类型来处理文本大数据
类型 长度 tinytext 2^8-1B(256B) text 2^16-1B(64K) mediumtext 2^24-1B(16M) longtext 2^32-1B(4G)
2.把mp3保存到数据库中
CREATE TABLE t_music(
id int PRIMARY KEY AUTO_INCREMENT,
filename varchar(100),
data mediumblob
);
public class Demo
{
public static void main(String[] args) throws IOException, SQLException
{
fun1();
fun2();
}
/**
* 将mp3文件存入数据库
*/
public static void fun1() throws SQLException, IOException
{
/**
* 1.得到connection
* 2.给出sql模板,创建pstmt
* 3.设置sql模板中的参数
* 4.执行
*/
Connection connection = JdbcUtils.getConnection();
String sql = "INSERT INTO t_music VALUES(?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,1);
preparedStatement.setString(2,"xx.mp3");
/**
* 需要得到Blob
* 1.有文件,目标是Blob
* 2.先把文件变成byte[]
* 3.在使用byte[]创建Blob
*/
//把文件转换成byte[]
byte[] bytes = IOUtils.toByteArray(new FileInputStream("D:/xx.mp3"));
//使用byte[]创建Blob
Blob blob = new SerialBlob(bytes);
//设置参数
preparedStatement.setBlob(3,blob);
preparedStatement.executeUpdate();
}
/**
* 从数据库读取mp3
*/
public static void fun2() throws SQLException, IOException {
/**
* 1.得到connection
* 2.给出sql模板,创建pstmt
* 3.设置sql模板中的参数
* 4.执行
*/
Connection connection = JdbcUtils.getConnection();
String sql = "SELECT * FROM t_music";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
/**
* 5.获取rs中名为data的列数据
*/
if(resultSet.next())
{
Blob blob = resultSet.getBlob("data");
/**
* 把Blob变成硬盘上的文件
* 1.通过Blob得到输入流对象
* 2.自己创建输出流对象
* 3.把输入流的数据写入到输出流中
*/
InputStream inputStream = blob.getBinaryStream();
OutputStream outputStream = new FileOutputStream("C:/Download/xx.mp3");
IOUtils.copy(inputStream,outputStream);
}
}
}
注意,需在my.ini中添加配置:max_allowed_packet=10485760,才可存储较大文件
批处理
1.Statement批处理
- 批处理就是一批一批的处理,而不是一个一个的处理
- 批处理只针对更新(增、删、改)语句,而与查询语句无关
- 可以多次调用Statement类的addBatch(String sql)方法,把需要执行的所有SQL语句添加到一个“批”中,然后调用Statement类的executeBatch()方法来执行当前“批”中的语句
- void addBatch(String sql):添加一条语句到“批”中
- int[] executeBatch():执行“批”中的所有语句。返回值表示每条语句所影响的行数据
- void clearBatch():清空“批”中的所有语句
- 执行了“批”之后,“批”中的SQL语句就会被清空。也就是说,连续两次调用executeBatch()相当于只调用一次。
for(int i=0;i<1000;i++)
{
String number = "s_10" + i ;
String name = "stu" + i ;
int age = 20 + i ;
String gender = i%2==0?"male":"female";
String sql = "INSERT INTO stu
VALUES('"+number+"','"+name+"',"+age+",'"+gender"'")";
statement.addBatch(sql);
}
statement.executeBatch();
2.PrepareadStatement批处理
PreparedStatement的批处理有所不同,因为每个PreparedStatement对象都绑定一条SQL模板,所以向PreparedStatement中添加的不是SQL语句,而是给“?”赋值
/**
* pstmt对象内部有集合
* 1.用循环疯狂向pstmt中添加sql参数,它有自己的模板,使用一组参数与模板可以匹配出一条sql语句
* 2.调用它的执行批方法,完成向数据库发送
*/
public static void fun() throws SQLException
{
/**
* pstmt:
* >添加参数到批中
* >执行批
*/
Connection connection = JdbcUtils.getConnection();
String sql = "INSERT INTO t_user VALUES(?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//添加参数
for(int i=0;i<1000;i++)
{
preparedStatement.setString(1,"stu_"+ i);
preparedStatement.setString(2,"s" + i);
preparedStatement.addBatch(); //添加批,这一组参数就保存到集合中了
}
long start = System.currentTimeMillis();
preparedStatement.executeBatch(); //执行批
long end = System.currentTimeMillis();
System.out.println(end-start); //1485->17
}
附:
MySQL的预编译功能
预编译的好处
- 防SQL攻击
- 提高代码的可读性,可维护性
- 提高效率
MySQL执行预编译
MySQL执行预编译分为3步:
- 执行预编译语句
- 设置变量
- 执行语句
如果需要再次该语句,就不再需要编译语句了
- 设置变量
- 执行语句
使用Statement执行预编译
使用Statement执行预编译就是把上面的SQL语句执行一次
Connection connection = JdbcUtils.getConnetion();
Statement statement = connection.createStatement();
statement.executeUpdate("prepare myfun from 'select * from
users where username=?'");
statement.executeUpdate("set @str='xxx'");
ResultSet resultSet = statement.executeQuery("execute myfun using @str");
while(resultSet.next())
{
System.out.print(resultSet.getObject(1)+",");
System.out.println(resultSet.getObject(2));
}
resultSet.close();
statement.close();
connection.close();
useServerPrepStmts参数
默认使用PreparedStatement是不能执行预编译的,需要在url中给出useServerPrepStmts=true参数(MySQL Server 4.1之前的版本是不支持预编译的,在5.0.5之后的版本,默认是没有开启预编译功能的)
例如:jdbc:mysql://localhost:3306/users?useServerPrepStmts=true
这样才能保证mysql驱动会先把SQL语句发送给服务器进行预编译,然后在执行executeQuery()时只是把参数发送给服务器
cachePrepStmts参数
当使用不同的PreparedStatement对象来执行相同的SQL语句时,还是会出现编译两次的现象,这是因为驱动没有缓存编译后的函数key,导致二次编译。如果希望缓存编译后有函数的key,那么就要设置cachePrepStmts参数为true。例如:
jdbc:mysql://localhost:3306/users?useServerPrepStmts=true&cachePrepStmts=true
打开批处理
MySQL的批处理也需要通过参数来打开:rewriteBatchedStatements=true