目录

一、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 批处理操作


一、JDBC开发步骤

1.Java程序连接数据库

1.1引入MySQL驱动包

使用Java连接MySQL之前需要先引入MySQL驱动jar包。

创建Java项目,并引入MySQL驱动jar包到项目中,如下图示例:

1.2Java连接MySQL步骤

A、代码示例:

//注册驱动:把驱动类加载到内存中//注意:5.1版本驱动包中驱动类名:com.mysql.jdbc.Driver//8.0版本驱动类名:com.mysql.cj.jdbc.DriverClass.forName("com.mysql.cj.jdbc.Driver");//与数据库建立连接Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu","root","1234");//实现增删改查数据//.....//关闭连接:如果不再使用连接需要断开连接以释放资源(底层是TCP/IP协议和IO流操作)conn.close();//程序能正常编译执行表示连接成功,如果抛异常表示连接失败。

B、步骤说明:

  1. 注册驱动

    此步骤的目的是把驱动类加载到内存中,可以通过以下方式实现:

    //方式一:不推荐,因会导致注册驱动被执行两次(看源码),并且代码强依赖数据库驱动jarDriverManager.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

  2. 与数据库建立连接

    加载驱动程序后,可以使用DriverManager的重载方法getConnection创建Connection对象,每个Connection对象表示Java程序与数据库之间的一个物理连接。

    Connection conn = getConnection(String url,String user,String password);

其中不同数据库URL配置不同:

RDBMSJDBC驱动程序名称URL格式
MySQLcom.mysql.cj.jdbc.Driverjdbc:mysql://hostname / databaseName
ORACLEoracle.jdbc.driver.OracleDriverjdbc:oracle:thin:@ hostname:port Number:databaseName
DB2com.ibm.db2.jdbc.net.DB2Driverjdbc:db2:hostname:port Number / databaseName

URL格式说明:

协议:子协议://主机名:端口/数据库名?参数名1=参数值1&参数名2=参数值2 其中,如果主机是本机或端口是默认3306端口时,可以缺省。如:jdbc:mysql:///databaseName

示例:jdbc:mysql://localhost:3306/testdb” />//方式一: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″);

2 实现增删改查操作

2.1 添加数据

/*用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对象,用来执行sql4、执行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.获取StateMentString 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) */}}

2.2 修改数据

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) */

2.3 删除数据

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) */

2.4 查询数据

/*步骤:1、注册驱动2、获取数据库连接3、获取Statement对象,用来执行sql4、执行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();}}

二、JDBC处理相关问题

1 解决SQL注入问题

PrepareStatement接口是Statement的子接口,提供了更优秀的功能

返回值方法名含义
ResultSetexecuteQuery()执行预处理sql语句的查询操作
intexecuteUpdate()执行预处理sql语句的增删改操作
voidsetInt(int parameterIndex, int x)设置sql参数
voidsetFloat(int parameterIndex, float x)设置sql参数
voidsetString(int parameterIndex, String x)设置sql参数
voidsetDate(int parameterIndex, java.sql.Date x)设置sql参数
voidsetObject(int parameterIndex, Object x)设置sql参数

1.1、问题演示

(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.2、解决问题

(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();//此处不能传sqlSystem.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

2 JDBC事务处理

采用转账案例

/* * 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、执行sqlString 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();}}

3 获取自增长键值

/* * 我们通过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、执行sqlString 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, "测试项目数据");//执行sqlint 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();}}

4 批处理操作

/* * 批处理: * 批量处理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、执行sqlString 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}}