目录
一、JDBC开发步骤
1.Java程序连接数据库
1.1引入MySQL驱动包
1.2Java连接MySQL步骤
2 实现增删改查操作
2.1 添加数据
2.2 修改数据
2.3 删除数据
2.4 查询数据
二、JDBC处理相关问题
1 解决SQL注入问题
1.1、问题演示
1.2、解决问题
2 JDBC事务处理
3 获取自增长键值
4 批处理操作
使用Java连接MySQL之前需要先引入MySQL驱动jar包。
创建Java项目,并引入MySQL驱动jar包到项目中,如下图示例:
A、代码示例:
//注册驱动:把驱动类加载到内存中
//注意:5.1版本驱动包中驱动类名:com.mysql.jdbc.Driver
//8.0版本驱动类名:com.mysql.cj.jdbc.Driver
Class.forName("com.mysql.cj.jdbc.Driver");
//与数据库建立连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu","root","1234");
//实现增删改查数据
//.....
//关闭连接:如果不再使用连接需要断开连接以释放资源(底层是TCP/IP协议和IO流操作)
conn.close();
//程序能正常编译执行表示连接成功,如果抛异常表示连接失败。
B、步骤说明:
注册驱动
此步骤的目的是把驱动类加载到内存中,可以通过以下方式实现:
//方式一:不推荐,因会导致注册驱动被执行两次(看源码),并且代码强依赖数据库驱动jar
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//方式二:创建了两个Driver对象(见源码),并且强依赖数据库驱动
new com.mysql.cj.jdbc.Driver();
//方式三:推荐,反射方式,接收字符串参数,降低了对驱动类的依赖
Class.forName("com.mysql.cj.jdbc.Driver");
实际在JDK6之后
DriverManager
就已经可以实现自动注册驱动,如果手动注册了驱动,不再自动注册。但是仍然建议显示通过反射方式注册驱动。需要驱动包中此位置文件
META-INF/services/java.sql.Driver
中包含内容:com.mysql.cj.jdbc.Driver
与数据库建立连接
加载驱动程序后,可以使用DriverManager的重载方法getConnection
创建Connection对象,每个Connection对象表示Java程序与数据库之间的一个物理连接。
Connection conn = getConnection(String url,String user,String password);
其中不同数据库URL配置不同:
RDBMS | JDBC驱动程序名称 | URL格式 |
---|---|---|
MySQL | com.mysql.cj.jdbc.Driver |
jdbc:mysql://hostname / databaseName |
ORACLE | oracle.jdbc.driver.OracleDriver |
jdbc:oracle:thin:@ hostname:port Number:databaseName |
DB2 | com.ibm.db2.jdbc.net.DB2Driver |
jdbc:db2:hostname:port Number / databaseName |
URL格式说明:
协议:子协议://主机名:端口/数据库名?参数名1=参数值1&参数名2=参数值2 其中,如果主机是本机或端口是默认3306端口时,可以缺省。如:jdbc:mysql:///databaseName
示例:jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
使用某些版本驱动包,建立连接时可能还需要设置服务器时区参数:
jdbc:mysql://localhost:3306/dbname?serverTimezone=Asia/Shanghai
三个重载方法创建连接示例:
//方式一:
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testJDBC?user=root&password=1234");
//方式二:
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "1234");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testJDBC",info);
//方式三:(推荐方式)
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testJDBC","root","1234");
/*
用JDBC实现添加一条记录到atguigu数据库的t_department表中。
mysql> desc t_department;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| did | int | NO | PRI | NULL | auto_increment |
| dname | varchar(20) | NO | UNI | NULL | |
| description | varchar(200) | YES | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
mysql> select * from t_department;
+-----+--------+------------------+
| did | dname | description |
+-----+--------+------------------+
| 1 | 研发部 | 负责研发工作 |
| 2 | 人事部 | 负责人事管理工作 |
| 3 | 市场部 | 负责市场推广工作 |
| 4 | 财务部 | 负责财务管理工作 |
| 5 | 后勤部 | 负责后勤保障工作 |
| 6 | 测试部 | 负责测试工作 |
+-----+--------+------------------+
6 rows in set (0.00 sec)
步骤:
1、注册驱动
2、获取数据库连接
3、获取Statement对象,用来执行sql
4、执行sql,即执行Statement对象的方法:int executeUpdate(),(增删改操作时)
5、释放资源
*/
public class TestInsert {
public static void main(String[] args)throws Exception {
//1.注册驱动,把驱动类加载到内存中
Class.forName("com.mysql.cj.jdbc.Driver");
//2.创建数据库连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu","root","1234");
//3.获取StateMent
String sql = "insert into t_department values(null,'数据部门','数据部门简介')";
PreparedStatement pst = conn.prepareStatement(sql);
//4.执行SQL,插入数据,返回sql影响的记录数
int len = pst.executeUpdate();
System.out.println(len>0 ? "添加成功" : "添加失败");
//5.释放资源
pst.close();
conn.close();
/*
mysql> select * from t_department;
+-----+--------------+------------------+
| did | dname | description |
+-----+--------------+------------------+
| 1 | 研发部 | 负责研发工作 |
| 2 | 人事部 | 负责人事管理工作 |
| 3 | 市场部 | 负责市场推广工作 |
| 4 | 财务部 | 负责财务管理工作 |
| 5 | 后勤部 | 负责后勤保障工作 |
| 6 | 测试部 | 负责测试工作 |
| 7 | 数据部门 | 数据部门简介 |
+-----+--------------+------------------+
7 rows in set (0.00 sec)
*/
}
}
public class TestUpdate {
public static void main(String[] args)throws Exception {
//1.把驱动类加载到内存中
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取数据库连接对象
String url = "jdbc:mysql://localhost:3306/atguigu";
Connection conn = DriverManager.getConnection(url, "root", "1234");
//3.获取Statement对象
String sql = "update t_department set description = 'xx' where did = 7";
PreparedStatement pst = conn.prepareStatement(sql);
//4.执行SQL,修改数据, 返回sql影响的记录数
int len = pst.executeUpdate();
System.out.println(len > 0 ? "修改成功" : "修改失败");
//5.释放资源
pst.close();
conn.close();
}
}
/*
mysql> select * from t_department;
+-----+--------------+------------------+
| did | dname | description |
+-----+--------------+------------------+
| 1 | 研发部 | 负责研发工作 |
| 2 | 人事部 | 负责人事管理工作 |
| 3 | 市场部 | 负责市场推广工作 |
| 4 | 财务部 | 负责财务管理工作 |
| 5 | 后勤部 | 负责后勤保障工作 |
| 6 | 测试部 | 负责测试工作 |
| 7 | 测试数据部门 | xx |
+-----+--------------+------------------+
7 rows in set (0.00 sec)
*/
public class TestDelete {
public static void main(String[] args)throws Exception {
//1.把驱动类加载到内存中
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取数据库连接对象
String url = "jdbc:mysql://localhost:3306/atguigu";
Connection conn = DriverManager.getConnection(url, "root", "1234");
//3.获取Statement对象
String sql = "delete from t_department where did = 7";
PreparedStatement pst = conn.prepareStatement(sql);
//4.执行SQL,删除数据, 返回sql影响的记录数
int len = pst.executeUpdate();
System.out.println(len > 0 ? "删除成功" : "删除失败");
//5.释放资源
pst.close();
conn.close();
}
}
/*
mysql> select * from t_department;
+-----+--------+------------------+
| did | dname | description |
+-----+--------+------------------+
| 1 | 研发部 | 负责研发工作 |
| 2 | 人事部 | 负责人事管理工作 |
| 3 | 市场部 | 负责市场推广工作 |
| 4 | 财务部 | 负责财务管理工作 |
| 5 | 后勤部 | 负责后勤保障工作 |
| 6 | 测试部 | 负责测试工作 |
+-----+--------+------------------+
6 rows in set (0.00 sec)
*/
/*
步骤:
1、注册驱动
2、获取数据库连接
3、获取Statement对象,用来执行sql
4、执行sql,即执行Statement对象的方法:
(1)int executeUpdate():执行insert,update,delete等更新数据库数据的sql
(2)ResultSet executeQuery():执行select查询的sql,返回一个结果集
(3)boolean execute():可以用来执行DDL语句
5、遍历结果集ResultSet:
boolean next():判断是否还有下一行
getObject(字段名或序号),getString(字段名或序号),getInt(字段名或序号)等
6、释放资源
*/
public class TestSelect {
public static void main(String[] args)throws Exception {
//1.注册驱动,把驱动类加载到内存中
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取数据库连接对象
String url = "jdbc:mysql://localhost:3306/atguigu";
Connection conn = DriverManager.getConnection(url, "root", "1234");
//3.获取Statement对象
String sql = "select * from t_department";
PreparedStatement pst = connn.prepareStatement(sql);
//4.执行查询SQL,返回查询结果
ResultSet resultSet = pst.executeQuery();
//5.遍历结果集
while(rs.next()){ //while循环一次,迭代一行,遍历一行
int did = rs.getInt("did");//get一次得到一个单元格的数据
String dname = rs.getString("dname");
String decription = rs.getString("description");
System.out.println(did +"\t" + dname +"\t" + decription);
}
//释放资源
rs.close();
pst.close();
conn.close();
}
}
PrepareStatement接口是Statement的子接口,提供了更优秀的功能
返回值 | 方法名 | 含义 |
---|---|---|
ResultSet | executeQuery() | 执行预处理sql语句的查询操作 |
int | executeUpdate() | 执行预处理sql语句的增删改操作 |
void | setInt(int parameterIndex, int x) | 设置sql参数 |
void | setFloat(int parameterIndex, float x) | 设置sql参数 |
void | setString(int parameterIndex, String x) | 设置sql参数 |
void | setDate(int parameterIndex, java.sql.Date x) | 设置sql参数 |
void | setObject(int parameterIndex, Object x) | 设置sql参数 |
... | ... | ... |
(1)SQL语句拼接
//提取用户名和密码变量,模拟登录操作
String name = "tom";
String password = "123456";
String sql =
"select * from users where name='" + name + "' and password='" + password + "'";
System.out.println("sql="+sql);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
(2)SQL注入
String name = "tom";
String password="' or '1'='1";// 如果登录时键盘录入密码为' or '1'='1时,结果登录成功
String sql =
"select * from users where name='" + name + "' and password='" + password + "'";
System.out.println("sql="+sql);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
(3)处理blob等类型的数据
如果数据库表中字段定义是blob类型时,需要写入的是二进制数据,即需要通过字节流写入二进制数据,这时无法把数据直接拼接成sql字符串。
(1)避免sql拼接
String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)";
PreparedStatement pst = conn.prepareStatement(sql);//这里要传带?的sql,然后mysql端就会对这个sql进行预编译
//设置?的具体值
/*pst.setString(1, name);
pst.setString(2, password);
pst.setString(3, email);
pst.setDouble(4, birthday);*/
pst.setObject(1, name);
pst.setObject(2, password);
pst.setObject(3, email);
pst.setObject(4, birthday);
int len = pst.executeUpdate();//此处不能传sql
System.out.println(len);
(2)不会有sql注入问题
//即使输入' or '1'= '1也没问题
String sql = "select * from users where name=? and password=?";
String name = "tom";
String password = "123123";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,name );
pstmt.setString(2,password );
ResultSet rs = pstmt.executeQuery();
(3)处理blob类型的数据
//pic字段为blob类型
String sql = "insert into users(name,pic) value(?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,"tom" );
pstmt.setBlob(2,new FileInputStream("D:/1.jpeg"));
int i = pstmt.executeUpdate();
System.out.println(i > 0 ? "成功" : "失败");
注意两个问题:
①my.ini关于上传的字节流文件有大小限制,可以在my.ini中修改变量max_allowed_packet值
max_allowed_packet=16M
②每一种blob有各自大小限制:
tinyblob:255字节、blob:65k、mediumblob:16M、longblob:4G
采用转账案例
/*
* mysql默认每一个连接是自动提交事务的。
* 那么当我们在JDBC这段,如果有多条语句想要组成一个事务一起执行的话,那么在JDBC这边怎么设置手动提交事务呢?
* (1)在执行之前,设置手动提交事务
* Connection的对象.setAutoCommit(false)
* (2)成功:
* Connection的对象.commit();
* 失败:
* Connection的对象.rollback();
*
* 补充说明:
* 为了大家养成要的习惯,在关闭Connection的对象之前,把连接对象设置回自动提交
* (3)Connection的对象.setAutoCommit(true)
*
* 因为我们现在的连接是建立新的连接,那么如果没有还原为自动提交,没有影响。
* 但是我们后面实际开发中,每次获取的连接,不一定是新的连接,而是从连接池中获取的旧的连接,而且你关闭也不是真关闭,
* 而是还给连接池,供别人接着用。以防别人拿到后,以为是自动提交的,而没有commit,最终数据没有成功。
*/
public class TestTransaction {
public static void main(String[] args) throws Exception{
/*
* 一般涉及到事务处理的话,那么业务逻辑都会比较复杂。
* 例如:购物车结算时:
* (1)在订单表中添加一条记录
* (2)在订单明细表中添加多条订单明细的记录(表示该订单买了什么东西)
* (3)修改商品表的销量和库存量
* ...
* 那么我们今天为了大家关注事务的操作,而不会因为复杂的业务逻辑的影响导致我们的理解,那么我们这里故意
* 用两条修改语句来模拟组成一个简单的事务。
* update t_department set description = 'xx' where did = 2;
* update t_department set description = 'yy' where did = 3;
*
* 我希望这两条语句要么一起成功,要么一起回滚
* 为了制造失败,我故意把第二条语句写错
* update t_department set description = 'yy' (少了where) did = 3;
*/
//1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "1234");
//设置手动提交事务
conn.setAutoCommit(false);
//3、执行sql
String sql1 = "update t_department set description = 'xx' where did = 2";
String sql2 = "update t_department set description = 'yy' did = 3";//这句错的
//使用prepareStatement的sql也可以不带问号?
PreparedStatement pst = null;
try {
pst = conn.prepareStatement(sql1);
int len = pst.executeUpdate();
System.out.println("第一条:" + (len>0?"成功":"失败"));
pst = conn.prepareStatement(sql2);
len = pst.executeUpdate();
System.out.println("第二条:" + (len>0?"成功":"失败"));
//都成功了,就提交事务
System.out.println("提交");
conn.commit();
} catch (Exception e) {
System.out.println("回滚");
//失败要回滚
conn.rollback();
}
//4、关闭
pst.close();
conn.setAutoCommit(true);//还原为自动提交
conn.close();
}
}
/*
* 我们通过JDBC往数据库的表格中添加一条记录,其中有一个字段是自增的,那么在JDBC这边怎么在添加之后直接获取到这个自增的值
* PreparedStatement是Statement的子接口。
* Statement接口中有一些常量值:
* (1)Statement.RETURN_GENERATED_KEYS
*
* 要先添加后获取到自增的key值:
* (1)PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
* (2)添加sql执行完成后,通过PreparedStatement的对象调用getGeneratedKeys()方法来获取自增长键值,遍历结果集
* ResultSet rs = pst.getGeneratedKeys();
*/
public class TestAutoIncrement {
public static void main(String[] args) throws Exception{
//1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
//3、执行sql
String sql = "insert into t_department values(null,?,?)";
/*
* 这里在创建PreparedStatement对象时,传入第二个参数的作用,就是告知服务器端
* 当执行完sql后,把自增的key值返回来。
*/
PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
//设置?的值
pst.setObject(1, "测试部");
pst.setObject(2, "测试项目数据");
//执行sql
int len = pst.executeUpdate();//返回影响的记录数
if(len>0){
//从pst中获取到服务器端返回的键值
ResultSet rs = pst.getGeneratedKeys();
//因为这里的key值可能多个,因为insert语句可以同时添加多行,所以用ResultSet封装
//这里因为只添加一条,所以用if判断
if(rs.next()){
Object key = rs.getObject(1);
System.out.println("自增的key值did =" + key);
}
}
//4、关闭
pst.close();
conn.close();
}
}
/*
* 批处理:
* 批量处理sql
*
* 例如:
* (1)订单明细表的多条记录的添加
* (2)批量添加模拟数据
* ...
*
* 不用批处理,和用批处理有什么不同?
* 批处理的效率很多
*
* 如何进行批处理操作?
* (1)在url中要加一个参数
* rewriteBatchedStatements=true,默认值false会发送多次sql请求
* 那么我们的url就变成了 jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
* 这里的?,表示?后面是客户端给服务器端传的参数,多个参数直接使用&分割
* (2)调用方法不同
* pst.addBatch();
* int[] all = pst.executeBatch();
*
* 注意:如果批量添加时,insert使用values,不要使用value,否则相当于发送多次sql执行
*/
public class TestBatch {
public static void main(String[] args) throws Exception{
long start = System.currentTimeMillis();
//例如:在部门表t_department中添加1000条模拟数据
//1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true", "root", "123456");
//3、执行sql
String sql = "insert into t_department values(null,?,?)";
PreparedStatement pst = conn.prepareStatement(sql);
//设置?的值
for (int i = 1; i <=1000; i++) {
pst.setObject(1, "模拟部门"+i);
pst.setObject(2, "模拟部门的简介"+i);
pst.addBatch();//添加到批处理一组操作中,攒一块处理
/* if(i % 500 == 0){//有时候也攒一部分,执行一部分
//2.执行
pst.executeBatch();
//3.清空
pst.clearBatch();
}*/
}
//执行批处理操作
pst.executeBatch();
//4、关闭
pst.close();
conn.close();
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));//耗时:821
}
}