目录
一、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、步骤说明:
注册驱动
此步骤的目的是把驱动类加载到内存中,可以通过以下方式实现:
//方式一:不推荐,因会导致注册驱动被执行两次(看源码),并且代码强依赖数据库驱动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
与数据库建立连接
加载驱动程序后,可以使用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” />//方式一: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的子接口,提供了更优秀的功能
返回值 方法名 含义 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.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}}