文章适合于刚入行不久或即将开始学习JAVA的小伙伴,你将看到鲜活的真实错误案例、代码开发需求清单,还有学习的注意事项。
JDBC概述
JDBC概述 | Java Database Connectivity的缩写,即JAVA数据库连接,规范客户端如何访问第三方数据库。 |
学习资源路径 | [视频教程,5个半小时]:https://www.bilibili.com/video/BV1Bt41137iB (动力节点杜老师,课讲得还是很赞的!) |
前置学习 | JAVA语言基础有全面的学习、熟悉mysql增删改查的核心代码 |
核心主题 | 实现JAVA对mysql数据库的DQL(查)、DML(增、删、改),以及TCL(事务控制) |
完整六步骤 | 注册驱动-->获取连接-->获取数据库操作对象-->执行访问-->处理结果集-->释放资源 |
JDBC应用开发框架
注:JDBC本质是很多工具类的集成,意味着很多代码不仅复用、同时服务多个应用场景,需要结合灵活配置;
本节重点阐述图示第一层、第二层的核心代码错误案例;第三层、第四层在下节Servlet核心练习题进行说明。
核心案例一:JDBC基础层--注册数据库驱动、获取数据库连接
1、注册数据库驱动适合参考底层写成静态代码;
2、IO流结合Properties属性配置文件,实现灵活访问不同数据库,而不用修改代码和重新部署。
错误前代码 | 修改后代码 |
public class JDBCutil { private static Connection conn;//下方定义静态方法,此处也要对应静态变量 private static PreparedStatement ps; private static ResultSet rs; //注册驱动写成静态方法,实现mysql驱动注册(不需要再Driver实例化)一旦加载就会运行此块而且驱动一直存在。 static { try { Class c = class.forName(com.mysql.jdbc.DriverManage);//错误标记_Class大写;括号类是方法传参要加引号; 而不是DriverManage } catch (ClassNotFoundException e) { e.printStackTrace();} } public static Connection getConnection() throws IOException, SQLException {// Properties pro = new Properties(); FileInputStream fis = null; fis = new FileInputStream("/homework/src/jdbc.properties");//错误标记_有时候报错是因为没有写完整路径 pro=fis_read();//错误标记_ pro=fis_read(),虽然是形似 String url = pro.getProperty("url"); String username = pro.getProperty("username"); String password = pro.getProperty("password"); Connection conn = DriverManager.getConnection(url, username, password); return conn; } } //-----------以下是对应的jdbc.properties配置文件代码-------------------------------------// url=jdbc:mysql://localhost:3306/JDBCTest##错误标记_有中文时要在配置文件的url字符串加上“?characterEncoding=gbk” username=root password=123 | public class JDBCutil { private static Connection conn;//下方定义静态方法,此处也要对应静态变量 private static PreparedStatement ps; private static ResultSet rs; //注册驱动写成静态方法,实现mysql驱动注册(不需要再Driver实例化)一旦加载就会运行此块而且驱动一直存在。 static { try { Class c = Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace();} } public static Connection getConnection() throws IOException, SQLException {// Properties pro = new Properties(); FileInputStream fis = null; fis = new FileInputStream("/home/psdz/Documents/05javascript/ideasave/Servlet/homework/src/jdbc.properties"); pro.load(fis); String url = pro.getProperty("url"); String username = pro.getProperty("username"); String password = pro.getProperty("password"); Connection conn = DriverManager.getConnection(url, username, password); return conn; } } //-----------以下是对应的jdbc.properties配置文件代码-------------------------------------// url=jdbc:mysql://localhost:3306/JDBCTest?useUnicode=true&characterEncoding=utf-8 username=root password=123 |
核心案例二:JDBC服务--数据库新增数据
重点要掌握INSERT语句的提前编译和占位补值等,另外实际项目中经常将查询的数据落到一个实体类,并存在集合中。
错误前代码 | 修改后代码 |
Connection conn = JDBCutil.getConnection();//错误标记_不适合作为全局变量,因为服务层每个方法都会涉及释放资源,再调用会报“资源关闭后不能再操作的错误” public static int insertData(USERS ur) throws IOException, SQLException { String sql = "INSERT INTO USERS (UserName,PassWord,Sex,Email) VALUES(?,?,?,?)"; int tag = 0; try { ps = conn.prepareStatement(sql); ps.setString(1, ur.getUserName()); ps.setString(2, ur.getPassWord()); ps.setString(3, ur.getSex().charAt(0));//错误标记_避免使用char类型,尽管此处不报错,但后面还有地方会涉及到此处的隐患 ps.setString(4, ur.getEmail()); tag = ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } close();//错误标记_null状态下不用关闭,也因此写在try内 if (tag > 0) { System.out.println("成功存储数据到JDBC"); return 1; }else { return 0; } } //USERS类包含UserId、UserName、PassWord、Sex、Email属性,一级封装方法。省略具体代码 | public static int insertData(USERS ur) throws IOException, SQLException { String sql = "INSERT INTO USERS (UserName,PassWord,Sex,Email) VALUES(?,?,?,?)"; int tag = 0; try { conn = JDBCutil.getConnection(); ps = conn.prepareStatement(sql); ps.setString(1, ur.getUserName()); ps.setString(2, ur.getPassWord()); ps.setString(3, ur.getSex()); ps.setString(4, ur.getEmail()); tag = ps.executeUpdate(); close(); } catch (SQLException e) { e.printStackTrace(); } if (tag > 0) { System.out.println("成功存储数据到JDBC"); return 1; }else { return 0; } } //USERS类包含UserId、UserName、PassWord、Sex、Email属性,一级封装方法。省略具体代码 |
核心案例三:JDBC服务--数据库查询
重点要掌握SELECT的提前编译和占位补值等,另外实际项目中经常将查询的数据落到一个实体类,并存在集合中。
错误前代码 | 修改后代码 |
public static List queryData(){ String sql = "SELECT * FROM USERS WHERE existTag = 1";//existTag逻辑删除标签 try { conn = JDBCutil.getConnection(); ps = conn.prepareStatement(sql); rs = ps.executeQuery(); List mylist = new ArrayList(); while(rs.next()){ USERS ur = new USERS(); ur.setUserId(rs.getInt("userId"));//错误标记_字段首字母大写,否则封装的方法命名看起来会有区别 ur.setUserName(rs.getString("UserName")); ur.setPassWord(rs.getString("PassWord")); ur.setSex(rs.getString("Sex")); ur.setEmail(rs.getString("Email")); mylist.add(ur); } } catch (SQLException | IOException e) { e.printStackTrace(); } close();//错误标记_null状态下不用关闭,也因此要写在try内 return mylist; //错误标记_null状态下不用返回list,也因此要写在try内 return null;//引用方要配if(非null) } //USERS类包含UserId、UserName、PassWord、Sex、Email属性,一级封装方法。省略具体代码 | public static List queryData(){ String sql = "SELECT * FROM USERS WHERE existTag = 1";//existTag逻辑删除标签 try { conn = JDBCutil.getConnection(); ps = conn.prepareStatement(sql); rs = ps.executeQuery(); List mylist = new ArrayList(); while(rs.next()){ USERS ur = new USERS(); ur.setUserId(rs.getInt("UserId")); ur.setUserName(rs.getString("UserName")); ur.setPassWord(rs.getString("PassWord")); ur.setSex(rs.getString("Sex")); ur.setEmail(rs.getString("Email")); mylist.add(ur); } close(); return mylist; } catch (SQLException | IOException e) { e.printStackTrace(); } return null;//引用方要配if(非null) } //USERS类包含UserId、UserName、PassWord、Sex、Email属性,一级封装方法。省略具体代码 |
核心案例四:JDBC服务--数据库更新&逻辑删除
逻辑删除是一种涉及规范,不让存入的数据被真实删除。通过加一个标签字段实现对“数据是否显示”的一个条件判断。
实际代码 |
public static int deleteData(String wheresentence){ //物理删除:DELETE FROM table_name [WHERE Clause];逻辑删除改标记 String sql = "UPDATE USERS SET ExistTag=0 " + wheresentence;//错误标记_SQL字符串末尾不能少一个空格 int rowtag = 0; try { conn = JDBCutil.getConnection(); ps = conn.prepareStatement(sql); rowtag = ps.executeUpdate(); close(); } catch (SQLException | IOException e) { e.printStackTrace(); } if (rowtag > 0) { System.out.println("成功逻辑删除数据"); } return rowtag;} |
核心案例五:JDBC服务--事务控制
事务控制是为了保持数据的完整性,避免一批数据中个别异常数据影响中断后,实际改动了前面一部分数据。
属性setAutoCommit()默认是true,要在操作数据库前改成false,并在操作结束后提交。
实际代码 |
public static void main(String[] args) throws IOException, SQLException { var conn = JDBCutil.getConnection(); PreparedStatement ps =null; ResultSet rs = null; try { conn.setAutoCommit(false); String sql = "update t_trans SET AccountBalance=AccountBalance+? where AccountName=?"; ps = conn.prepareStatement(sql); //第一次占位改值 ps.setString(2,"zs"); ps.setDouble(1,-5000); int tag = ps.executeUpdate(); System.out.println("修改"+tag+"行数据进行中"); //制造空指针异常,为了看事务被中断的效果 //String k = null; //k.toString(); //第二次占位改值 ps.setString(2,"zs"); ps.setDouble(1,10000); tag += ps.executeUpdate(); System.out.println(tag == 2? "连续修改两次数值成功":"连续修改两次数值失败"); //事务自动提交重新开启 conn.commit(); } catch (SQLException throwables) { throwables.printStackTrace(); } //释放资源 JDBCutil.close(rs,ps,conn); } |
核心案例六:JDBC服务--释放资源写成单独方法
释放资源的方法适合和其他方法一起写在JDBC服务层上,过程中涉及到的对象由全局变量生成,减少不必要的代码重复
释放的顺序与对象对象的顺序相反,依次是:ResultSet-->PreparedStatement-->Connection
实际代码 |
public static void close(ResultSet rs, Statement ps, Connection conn){ if(rs!=null){ try{ rs.close(); }catch(SQLException e){ e.printStackTrace(); } } if(ps!=null){ try{ ps.close(); }catch(SQLException e){ e.printStackTrace(); } } if(conn!=null){ try{ conn.close(); }catch(SQLException e){ e.printStackTrace(); } } } |
最后,谢谢查看,更多同类核心实战案例请查看往期文章~