JDBC
JDBC
JDBC
一、概念
JDBC (Java Database Connectivity )指使用Java连接数据库,对数据库实现CRUD操作。
二、核心思想
Java定义一套操作数据库的规范(接口),各数据库厂商去实现该接口,保证程序员在使用上的统一。
三、驱动和接口
API(类和接口):在java.sql包中
- DriverManager类:获得数据库连接
- Connection接口:连接的接口
- Statement接口以及其子接口PreparedStatment接口:操作数据库语句
- ResultSet接口:查询时返回的结果集
- SQLException类:数据库操作过程中的异常处理
驱动:
- ODBC:桥接,使用系统去连接数据库,能够通用的连接大部分数据库,但是性能不好,而且功能不完整。
- JDBC:数据库厂商提供的直连驱动包。
- mysql-connector-java-5.1.40.jar:连接MySQL5的驱动
- mysql-connector-java-8.0.20.jar:连接MySQL8的驱动
四、基本操作
操作步骤:
0、将驱动包导入
加载驱动
获得连接
通过连接获得SQL的执行对象
通过执行对象执行SQL
处理执行结果
释放资源
public class JobsDAO { public static void main(String[] args) { // 1、加载驱动 try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } final String url = "jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8"; final String username = "root"; final String password = "root"; try ( // 2、获得连接 final Connection connection = DriverManager.getConnection(url, username, password); // 3、获取执行sql对象 final Statement statement = connection.createStatement() ){ System.out.println(connection);// final String sql = "INSERT INTO t_jobs VALUES ('2', 'BBB', 2000, 10000);";// // 4、执行SQL语句(更新操作,增删改),返回影响的行数// final int count = statement.executeUpdate(sql);// // 5、处理结果// if (count > 0){// System.out.println("执行成功");// }else{// System.out.println("执行失败");// } final String sql = "SELECT COUNT(1) FROM t_jobs"; // 4、执行SQL语句(查询操作),返回一个多行多列的结果集 final ResultSet resultSet = statement.executeQuery(sql); // 5、处理结果 // 循环每一行 while (resultSet.next()){ // 获取当前行每一列的值 // 可以通过列的序号来获取,也可以通过列名来获取,推荐使用名称,COUNT(1)这种名称不太好使用的,推荐使用别名 final long count = resultSet.getLong(1); System.out.println(count); } }catch (SQLException e){ e.printStackTrace(); } }}
五、ResultSet使用
用来封装查询结果集。
注意:ResultSet是仅向前操作的对象,通过next方法向前操作,不能回退。
当移动到当前行时,使用getXxx()方法来获得某个列的值,方法的参数为列名或者序号(从1开始)。
public class JobsDAO { public static void main(String[] args) { // 1、加载驱动 try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } final String url = "jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8"; final String username = "root"; final String password = "root"; try ( // 2、获得连接 final Connection connection = DriverManager.getConnection(url, username, password); // 3、获取执行sql对象 final Statement statement = connection.createStatement() ){ System.out.println(connection); final String sql = "SELECT COUNT(1) FROM t_jobs"; // 4、执行SQL语句(查询操作),返回一个多行多列的结果集 final ResultSet resultSet = statement.executeQuery(sql); // 5、处理结果 // 循环每一行 while (resultSet.next()){ // 获取当前行每一列的值 // 可以通过列的序号来获取,也可以通过列名来获取,推荐使用名称,COUNT(1)这种名称不太好使用的,推荐使用别名 final long count = resultSet.getLong(1); System.out.println(count); } }catch (SQLException e){ e.printStackTrace(); } }}
六、SQL注入
利用拼接SQL字符串中的单引号,来注入关键字来实现非法操作。
例如:一个简单登录操作如下:
System.out.println(connection);String u = "zhangsan";String p = "123456";final String sql = "SELECT COUNT(1) FROM stu WHERE username = '"+u+"' AND pwd = '"+p+"'";// 4、执行SQL语句(查询操作),返回一个多行多列的结果集final ResultSet resultSet = statement.executeQuery(sql);// 5、处理结果// 循环每一行while (resultSet.next()){ // 获取当前行每一列的值 // 可以通过列的序号来获取,也可以通过列名来获取,推荐使用名称,COUNT(1)这种名称不太好使用的,推荐使用别名 final long count = resultSet.getLong(1); System.out.println(count > 0);}
上面的代码执行看似没有问题,但是当用户将密码输入
sdadsd' OR '1' = '1
,无论用户名是什么,都会显示登录成功。就是简单的SQL注入攻击。
七、PreparedStatement
PreparedStatement接口是Statement接口的子接口。
特点:
- 预编译语句,效率高
- 安全,避免SQL注入
- 动态填充内容,可以重复使用
// ?表示占位符final String sql = "INSERT INTO stu(name, username, pwd) VALUES (?, ?, ?);";try ( // 2、获得连接 final Connection connection = DriverManager.getConnection(url, username, password); // 3、获取执行sql对象 final PreparedStatement preparedStatement = connection.prepareStatement(sql)){ for (int i = 0; i < 5; i++) { // 设置参数 preparedStatement.setString(1, "AAA" + i); preparedStatement.setString(2, "BBBB" + i); preparedStatement.setString(3, "CCCC" + i); // 4、执行SQL语句(更新操作,增删改),返回影响的行数 final int count = preparedStatement.executeUpdate(); // 5、处理结果 if (count > 0){ System.out.println("执行成功"); }else{ System.out.println("执行失败"); } }}catch (SQLException e){ e.printStackTrace();}
八、封装工具类
封装连接类:
public class DBConnection { static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8", "root", "root"); }}
上面的内容,无法热修改,因为需要修改源代码,所以对于可能在上线后还需要修改的参数,应该使用配置文件。如下处理:
dbconfig.properties
jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf-8jdbc.user=rootjdbc.password=root
private final static Properties PROPERTIES = new Properties(); static { try { // 读取并加载配置文件 final InputStream inputStream = DBConnection1.class.getResourceAsStream("/dbconfig.properites"); PROPERTIES.load(inputStream); Class.forName(PROPERTIES.getProperty("jdbc.driver")); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(PROPERTIES.getProperty("jdbc.url"), PROPERTIES.getProperty("jdbc.user"), PROPERTIES.getProperty("jdbc.password")); }}
九、封装DAO层
ORM:Object Relational Mapping对象关系映射。
是指将实体类的对象与数据库中的一行记录相对应。
DAO:Data Access Object数据访问对象,即数据库操作类的对象。
@Data@AllArgsConstructor@NoArgsConstructorpublic class Student { private int id; private String name; private String username; private String pwd;}
public interface StudentDAO { boolean save(Student student); boolean delete(int id); boolean update(Student student); Student findById(int id); List<Student> findAll();}
public interface Constants { String SQL_STUDENT_SAVE = "INSERT INTO stu(name, username, pwd) VALUES (?,?,?)"; String SQL_STUDENT_UPDATE = "UPDATE stu SET name = ?, username = ?, pwd = ? WHERE id = ?"; String SQL_STUDENT_DELETE = "DELETE FROM stu WHERE id = ?"; String SQL_STUDENT_FIND_BY_ID = "SELECT id, name, username, pwd FROM stu WHERE id = ?"; String SQL_STUDENT_FIND_ALL = "SELECT id, name, username, pwd FROM stu";}
public class StudentDAOImpl implements StudentDAO { @Override public boolean save(Student student) { try( Connection connection = DBConnection.getConnection(); final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_STUDENT_SAVE); ) { // 设置参数 preparedStatement.setString(1, student.getName()); preparedStatement.setString(2, student.getUsername()); preparedStatement.setString(3, student.getPwd()); return preparedStatement.executeUpdate() > 0; }catch (SQLException e){ e.printStackTrace(); } return false; } @Override public boolean delete(int id) { try( Connection connection = DBConnection.getConnection(); final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_STUDENT_DELETE); ) { // 设置参数 preparedStatement.setInt(1, id); return preparedStatement.executeUpdate() > 0; }catch (SQLException e){ e.printStackTrace(); } return false; } @Override public boolean update(Student student) { try( Connection connection = DBConnection.getConnection(); final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_STUDENT_UPDATE); ) { // 设置参数 preparedStatement.setString(1, student.getName()); preparedStatement.setString(2, student.getUsername()); preparedStatement.setString(3, student.getPwd()); preparedStatement.setInt(4, student.getId()); return preparedStatement.executeUpdate() > 0; }catch (SQLException e){ e.printStackTrace(); } return false; } @Override public Student findById(int id) { try( Connection connection = DBConnection.getConnection(); final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_STUDENT_FIND_BY_ID); ) { // 设置参数 preparedStatement.setInt(1, id); final ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()){ return new Student( resultSet.getInt("id"), resultSet.getString("name"), resultSet.getString("username"), resultSet.getString("pwd") ); } }catch (SQLException e){ e.printStackTrace(); } return null; } @Override public List<Student> findAll() { List<Student> list = new ArrayList<>(); try( Connection connection = DBConnection.getConnection(); final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_STUDENT_FIND_ALL); ) { final ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()){ final Student student = new Student( resultSet.getInt("id"), resultSet.getString("name"), resultSet.getString("username"), resultSet.getString("pwd") ); list.add(student); } }catch (SQLException e){ e.printStackTrace(); } return list; }}
十、Service层
业务逻辑层,也写作Business(简写Biz)。
代表软件中的功能。
作用:一般情况下,一个业务就是一个方法。在业务中调用数据库操作。
十一、三层架构
- 界面层View
- 业务层Service
- 数据访问层DAO
调用顺序:View -> Service -> DAO
作用:需要修改时,只需要考虑其中某一层。
高内聚,低耦合:将代码中相似或相同的内容,抽取出相似的部分,写到一起,其他的对他进行调用,称为高内聚。低耦合,是指降低代码中相互的依赖性。
包的名称方式:
- entity:存放实体类
- dao:数据库操作
- service:业务逻辑
- view:界面
- util:帮助类
案例:学生信息管理系统
十二、事务
12.1 事务的基本使用
步骤:
connection.setAutoCommit(false);
connection.commit();在执行最后执行。
connection.rollback()在代码异常时执行。
- 如果在DAO中获取了连接,然后在Service中也获取连接,那么在多个连接中无法实现事务处理。
要想实现事务处理,必须在同一个连接中。为了实现同一个连接,有以下几种处理方式:
- 1、将连接作为参数传递,可以实现,但是会导致代码的侵入性。如果service调用service还是多个连接。会出错。
- 2、将连接存在一个公共位置,谁用谁取。看似可行,其实有线程安全问题,而且多线程操作名字相同会覆盖。
- 为了解决上面2的问题,又要实现1的不足(耦合度高),系统提供一个类ThreadLocal。
12.2 ThreadLocal【经典面试题】
每个线程通过ThreadLocal绑定一个map对象。
当前线程中的所有流程步骤都共享该map对象、
// ThreadLocal源码// 通过当前线程绑定的map设置值public void set(T value) { // 获得当前线程 Thread t = Thread.currentThread(); // 根据当前线程获得该线程绑定的map ThreadLocalMap map = getMap(t); if (map != null) // 将对象设置到当前map中,key为当前ThreadLocal对象 map.set(this, value); else // 如果没有,则通过该线程创建一个map createMap(t, value);}// 通过当前线程绑定的map获取值public T get() { // 获得当前线程 Thread t = Thread.currentThread(); // 根据当前线程获得该线程绑定的map ThreadLocalMap map = getMap(t); if (map != null) { // 通过key为当前ThreadLocal对象来获取存储的对象 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") // 返回该对象 T result = (T)e.value; return result; } } return setInitialValue();}
public class DBConnection { private static final Properties PROPERTIES = new Properties(); private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>(); static { final InputStream inputStream = DBConnection.class.getResourceAsStream("/dbconfig.properties"); try { PROPERTIES.load(inputStream); Class.forName(PROPERTIES.getProperty("jdbc.driver")); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { // 在当前线程绑定的map中获取连接 Connection connection = THREAD_LOCAL.get(); if (connection == null){ // 创建连接 connection = DriverManager.getConnection(PROPERTIES.getProperty("jdbc.url"), PROPERTIES.getProperty("jdbc.user"), PROPERTIES.getProperty("jdbc.password")); // 设置到当前线程绑定的map中 THREAD_LOCAL.set(connection); } return connection; } public static void closeAll(){ // 在当前线程绑定的map中获取连接 Connection connection = THREAD_LOCAL.get(); if (connection != null){ try { connection.close(); // 在当前线程绑定的map中删除连接 THREAD_LOCAL.remove(); } catch (SQLException e) { e.printStackTrace(); } } }}
public class AccountServiceImpl implements AccountService { private AccountDAO accountDAO = new AccountDAOImpl(); @Override public void transfer(Account from, Account to) { Connection connection = null; try{ connection = DBConnection.getConnection(); connection.setAutoCommit(false); accountDAO.decrease(from);// int i = 5 / 0; accountDAO.increase(to); connection.commit(); }catch (Exception e){ e.printStackTrace(); if (connection != null){ try { connection.rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); } } }finally { DBConnection.closeAll(); } }}
public class AccountDAOImpl implements AccountDAO { @Override public boolean increase(Account account) throws SQLException { Connection connection = DBConnection.getConnection(); final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_ACCOUNT_INCREASE); preparedStatement.setInt(1, account.getMoney()); preparedStatement.setString(2, account.getAccount()); return preparedStatement.executeUpdate() > 0; } @Override public boolean decrease(Account account) throws SQLException { Connection connection = DBConnection.getConnection(); final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_ACCOUNT_DECREASE); preparedStatement.setInt(1, account.getMoney()); preparedStatement.setString(2, account.getAccount()); return preparedStatement.executeUpdate() > 0; }}
十三、DAO的封装DaoUtils
public interface RowMapper<T> { T getMapper(ResultSet resultSet) throws SQLException;}
public class DAOUtils<T> { public boolean update(String sql, Object... args) throws SQLException { // 获得连接 final Connection connection = DBConnection.getConnection(); // 获得sql预编译操作对象 final PreparedStatement preparedStatement = connection.prepareStatement(sql); // 设置参数 for (int i = 0; i < args.length; i++) { preparedStatement.setObject(i + 1, args[i]); } // 执行 return preparedStatement.executeUpdate() > 0; } public List<T> queryList(String sql, RowMapper<T> mapper, Object... args) throws SQLException { List<T> list = new ArrayList<>(); final Connection connection = DBConnection.getConnection(); final PreparedStatement preparedStatement = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { preparedStatement.setObject(i + 1, args[i]); } final ResultSet resultSet = preparedStatement.executeQuery(); // 循环结果集 while (resultSet.next()){ // 封装结果集的行 T t = mapper.getMapper(resultSet); // 添加到集合中 list.add(t); } return list; } public T queryOne(String sql, RowMapper<T> mapper, Object... args) throws SQLException { final Connection connection = DBConnection.getConnection(); final PreparedStatement preparedStatement = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { preparedStatement.setObject(i + 1, args[i]); } final ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()){ T t = mapper.getMapper(resultSet); return t; } return null; }}
public class StudentDAO { private DAOUtils<Student> daoUtils = new DAOUtils(); public boolean save(Student student) throws SQLException { return daoUtils.update(Constants.SQL_STUDENT_SAVE, student.getName(), student.getSex(), student.getAge(), student.getPhone()); } public boolean update(Student student) throws SQLException { return daoUtils.update(Constants.SQL_STUDENT_UPDATE, student.getName(), student.getSex(), student.getAge(), student.getPhone(), student.getId()); } public boolean delete(int id) throws SQLException { return daoUtils.update(Constants.SQL_STUDENT_DELETE, id); } public List<Student> findAll() throws SQLException{ return daoUtils.queryList(Constants.SQL_STUDENT_FIND_ALL, new RowMapper<Student>() { @Override public Student getMapper(ResultSet resultSet) throws SQLException { return new Student( resultSet.getInt("id"), resultSet.getString("name"), resultSet.getString("sex"), resultSet.getInt("age"), resultSet.getString("phone") ); } }); } public Student findById(int id) throws SQLException{ return daoUtils.queryOne(Constants.SQL_STUDENT_FIND_BY_ID, resultSet -> new Student( resultSet.getInt("id"), resultSet.getString("name"), resultSet.getString("sex"), resultSet.getInt("age"), resultSet.getString("phone") ) , id); }}
十四、连接池
避免频繁创建和关闭连接。
连接池有很多种:DBCP、C3P0、druid(阿里出品)
driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/stu_sys?useUnicode=true&characterEncoding=utf-8username=rootpassword=root#初始化连接数initialSize=10#最大活动连接数maxActive=50#最小空闲连接minIdle=5#最大等待时间5秒maxWait=5000
上面的配置信息,key不能改,采用约定优于配置原则
约定优于配置:事先约定好key的名称,如果遵守约定,那么就无需配置,如果不遵守约定,就需要添加额外的配置。
public class DBConnection { private static final Properties PROPERTIES = new Properties(); private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>(); private static DataSource dataSource; // 数据源连接池 static { final InputStream inputStream = DBConnection.class.getResourceAsStream("/druid.properties"); try { PROPERTIES.load(inputStream); dataSource = DruidDataSourceFactory.createDataSource(PROPERTIES); } catch (Exception e) { e.printStackTrace(); } } public static DataSource getDataSource(){ return dataSource; } // dbutils类只有需要事务时才使用 public static Connection getConnection() throws SQLException { Connection connection = THREAD_LOCAL.get(); if (connection == null){ connection = dataSource.getConnection(); THREAD_LOCAL.set(connection); } return connection; } public static void closeAll() throws SQLException { Connection connection = THREAD_LOCAL.get(); if (connection != null){ // 在连接池中获取的连接,关闭时会还到池中再次使用 connection.close(); THREAD_LOCAL.remove(); } }}
十五、DBUtils帮助类用法
当没有事务时,可以直接使用:
private QueryRunner runner = new QueryRunner(DBConnection.getDataSource());
注意:结果集的封装,使用反射的方式需要字段名和属性名一致。
public class StudentDAOImpl implements StudentDAO { // DBUtils中的操作类的对象 // 当没有事务时,直接使用连接池对象 private QueryRunner runner = new QueryRunner(DBConnection.getDataSource()); @Override public boolean save(Student student) throws SQLException { return runner.update(Constants.SQL_STUDENT_SAVE, student.getName(), student.getSex(), student.getAge(), student.getPhone()) > 0; } @Override public boolean update(Student student) throws SQLException { return runner.update(Constants.SQL_STUDENT_UPDATE, student.getName(), student.getSex(), student.getAge(), student.getPhone(), student.getId()) > 0; } @Override public boolean delete(int id) throws SQLException { return runner.update(Constants.SQL_STUDENT_DELETE, id) > 0; }// @Override// public List findAll() throws SQLException { // // 通过反射封装结果集,但是需要字段名与属性名一致// return runner.query(Constants.SQL_STUDENT_FIND_ALL, new BeanListHandler<>(Student.class));// } // 当字段名与属性不一致时,可以使用以下办法: // 1、当个别属性不一致时,可以通过别名的方式 // 2、当很多属性不一致时,可以自己手动封装结果集 @Override public List<Student> findAll() throws SQLException { // 通过反射封装结果集,但是需要字段名与属性名一致 return runner.query(Constants.SQL_STUDENT_FIND_ALL, new ResultSetHandler<List<Student>>() { @Override public List<Student> handle(ResultSet resultSet) throws SQLException { List<Student> list = new ArrayList<>(); while (resultSet.next()){ Student student = new Student(resultSet.getInt("id"),resultSet.getString("name1"),resultSet.getString("sex"),resultSet.getInt("age"),resultSet.getString("phone") ); list.add(student); } return list; } }); } @Override public Student findById(int id) throws SQLException { return runner.query(Constants.SQL_STUDENT_FIND_BY_ID, new BeanHandler<>(Student.class), id); }}
当有事务时:
public class AccountDAOImpl implements AccountDAO { private QueryRunner runner = new QueryRunner(); @Override public boolean increase(Account account) throws SQLException { return runner.update(DBConnection.getConnection(), Constants.SQL_ACCOUNT_INCREASE, account.getMoney(), account.getAccount()) > 0; } @Override public boolean decrease(Account account) throws SQLException { return runner.update(DBConnection.getConnection(), Constants.SQL_ACCOUNT_DECREASE, account.getMoney(), account.getAccount()) > 0; }}
public class AccountServiceImpl implements AccountService { private AccountDAO accountDAO = new AccountDAOImpl(); @Override public void transfer(Account from, Account to) throws SQLException { Connection connection = null; try{ connection = DBConnection.getConnection(); connection.setAutoCommit(false); accountDAO.decrease(from); int i = 5 / 0; accountDAO.increase(to); connection.commit(); }catch (Exception e){ e.printStackTrace(); if (connection != null){ try { connection.rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); } } }finally { DBConnection.closeAll(); } }}
来源地址:https://blog.csdn.net/weixin_45427945/article/details/129916719
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341