
JDBC、连接池、反射与注解第一章.JDBC 基础1.1 JDBC 的介绍1.JDBC: Java Database Connectivity,Java 数据库连接。 2.作用: Java 提供的一套操作关系型数据库的标准 API。 3.特点: Java 定义接口和规范。 数据库厂商提供具体驱动实现。Java 程序 ↓ JDBC API MySQL 驱动 mysql-connector-j ↓ MySQL 数据库1.2 JDBC 依赖Maven 依赖dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependency旧坐标说明早期课程中常见mysql:mysql-connector-java新版本更常用com.mysql:mysql-connector-j。如果项目已有旧坐标学习阶段能正常连接即可。1.3 JDBC 四大核心对象对象类型作用DriverManager类管理驱动、获取连接Connection接口表示数据库连接Statement接口执行 SQL 语句ResultSet接口保存查询结果1.增删改: 需要 Connection、Statement。 2.查询: 需要 Connection、Statement、ResultSet。 3.所有资源用完都要关闭。1.4 JDBC 开发步骤1.注册驱动。 2.获取数据库连接。 3.准备 SQL。 4.获取执行 SQL 的对象。 5.执行 SQL。 6.处理结果集。 7.关闭资源。第二章.Statement 基础操作2.1 准备数据库表CREATE DATABASE IF NOT EXISTS jdbc_study; USE jdbc_study; CREATE TABLE user ( uid INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20), password VARCHAR(20) );2.2 注册驱动Class.forName(com.mysql.cj.jdbc.Driver);1.DriverManager.registerDriver(new Driver()) 不推荐。 2.new Driver() 时驱动内部静态代码块也会注册一次。 3.手动 registerDriver 可能导致重复注册。 4.Class.forName 可以加载驱动类,触发驱动注册。新版本驱动自动加载JDBC 4 之后驱动通常可以自动加载。但学习阶段写上Class.forName(...)有助于理解完整流程。2.3 获取数据库连接String url jdbc:mysql://localhost:3306/jdbc_study; String username root; String password root; Connection connection DriverManager.getConnection(url, username, password);1.url 格式: jdbc:mysql://主机名:端口号/数据库名 2.常见参数: useSSLfalse serverTimezoneAsia/Shanghai characterEncodingutf8 rewriteBatchedStatementstrue 3.多个参数之间使用 连接。String url jdbc:mysql://localhost:3306/jdbc_study?useSSLfalseserverTimezoneAsia/Shanghai;2.4 executeUpdate 执行增删改1.executeUpdate(String sql): 用于执行 INSERT、UPDATE、DELETE。 2.返回值: 受影响的行数。案例:新增用户import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; public class Demo01Insert { public static void main(String[] args) throws Exception { Class.forName(com.mysql.cj.jdbc.Driver); String url jdbc:mysql://localhost:3306/jdbc_study?useSSLfalseserverTimezoneAsia/Shanghai; String username root; String password root; Connection connection DriverManager.getConnection(url, username, password); String sql INSERT INTO user (username, password) VALUES (tom, 111); Statement statement connection.createStatement(); int rows statement.executeUpdate(sql); System.out.println(影响行数: rows); statement.close(); connection.close(); } }案例:修改和删除用户String updateSql UPDATE user SET password 222 WHERE username tom; int updateRows statement.executeUpdate(updateSql); String deleteSql DELETE FROM user WHERE username tom; int deleteRows statement.executeUpdate(deleteSql);2.5 executeQuery 执行查询1.executeQuery(String sql): 用于执行 SELECT 查询。 2.返回值: ResultSet 结果集。import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class Demo02Select { public static void main(String[] args) throws Exception { Class.forName(com.mysql.cj.jdbc.Driver); String url jdbc:mysql://localhost:3306/jdbc_study?useSSLfalseserverTimezoneAsia/Shanghai; Connection connection DriverManager.getConnection(url, root, root); String sql SELECT uid, username, password FROM user; Statement statement connection.createStatement(); ResultSet rs statement.executeQuery(sql); while (rs.next()) { int uid rs.getInt(uid); String name rs.getString(username); String pwd rs.getString(password); System.out.println(uid name pwd); } rs.close(); statement.close(); connection.close(); } }2.6 ResultSet 常用方法方法说明boolean next()移动到下一行有数据返回truegetInt(int columnIndex)按列序号获取整数getInt(String columnLabel)按列名获取整数getString(int columnIndex)按列序号获取字符串getString(String columnLabel)按列名获取字符串getObject(String columnLabel)按列名获取对象推荐按列名获取rs.getString(username)比rs.getString(2)更清晰也不容易因为 SQL 字段顺序变化而出错。第三章.JDBC 工具类本章目标把注册驱动、获取连接、关闭资源这类重复代码封装起来让业务代码只关注 SQL 本身。3.1 jdbc.propertiesdriverClasscom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/jdbc_study?useSSLfalseserverTimezoneAsia/Shanghai usernameroot passwordroot1.配置文件通常放在 src/main/resources。 2.key 名称可以自定义,但 Java 代码读取时要保持一致。 3.密码等敏感信息真实项目中不要提交到公开仓库。3.2 JDBCUtils 工具类import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class JDBCUtils { private static String url; private static String username; private static String password; private JDBCUtils() { } static { try { Properties properties new Properties(); InputStream in JDBCUtils.class.getClassLoader().getResourceAsStream(jdbc.properties); properties.load(in); Class.forName(properties.getProperty(driverClass)); url properties.getProperty(url); username properties.getProperty(username); password properties.getProperty(password); } catch (Exception e) { throw new RuntimeException(初始化 JDBC 工具类失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(Connection conn, Statement st, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st ! null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }3.3 使用工具类查询import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; public class Demo03UseUtils { public static void main(String[] args) { Connection conn null; Statement st null; ResultSet rs null; try { conn JDBCUtils.getConnection(); st conn.createStatement(); rs st.executeQuery(SELECT uid, username, password FROM user); while (rs.next()) { System.out.println(rs.getInt(uid) rs.getString(username)); } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.close(conn, st, rs); } } }3.4 try-with-resources 写法import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; public class Demo04TryWithResources { public static void main(String[] args) { String sql SELECT uid, username, password FROM user; try ( Connection conn JDBCUtils.getConnection(); Statement st conn.createStatement(); ResultSet rs st.executeQuery(sql) ) { while (rs.next()) { System.out.println(rs.getInt(uid) rs.getString(username)); } } catch (Exception e) { e.printStackTrace(); } } }第四章.PreparedStatement 预处理对象4.1 SQL 注入问题1.SQL 注入: 用户输入内容被拼接成 SQL 的一部分, 导致 SQL 语义被恶意改变。 2.问题根源: 使用字符串拼接 SQL。String sql SELECT * FROM user WHERE username username AND password password ;如果用户输入:username: tom password: OR 1 1拼接后的 SQL 可能变成:SELECT * FROM user WHERE username tom AND password OR 1 1;4.2 PreparedStatement 的介绍1.PreparedStatement: 预处理 SQL 对象,是 Statement 的子接口。 2.获取方式: Connection.prepareStatement(sql) 3.特点: a.SQL 中可以使用 ? 占位符 b.参数通过 setXxx 方法赋值 c.可以防止 SQL 注入 d.可读性和维护性更好方法说明setInt(int index, int value)给第几个?设置 intsetString(int index, String value)给第几个?设置 StringsetObject(int index, Object value)给第几个?设置任意对象executeUpdate()执行增删改executeQuery()执行查询4.3 PreparedStatement 登录案例import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.Scanner; public class Demo05Login { public static void main(String[] args) { Scanner scanner new Scanner(System.in); System.out.println(请输入用户名:); String username scanner.nextLine(); System.out.println(请输入密码:); String password scanner.nextLine(); String sql SELECT * FROM user WHERE username ? AND password ?; try ( Connection conn JDBCUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql) ) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { System.out.println(登录成功); } else { System.out.println(登录失败); } } } catch (Exception e) { e.printStackTrace(); } } }4.4 PreparedStatement 增删改查新增String sql INSERT INTO user (username, password) VALUES (?, ?); try ( Connection conn JDBCUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql) ) { ps.setString(1, jack); ps.setString(2, 222); int rows ps.executeUpdate(); System.out.println(rows); }修改String sql UPDATE user SET password ? WHERE username ?; try ( Connection conn JDBCUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql) ) { ps.setString(1, 333); ps.setString(2, jack); int rows ps.executeUpdate(); System.out.println(rows); }删除String sql DELETE FROM user WHERE username ?; try ( Connection conn JDBCUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql) ) { ps.setString(1, jack); int rows ps.executeUpdate(); System.out.println(rows); }查询并封装对象public class User { private Integer uid; private String username; private String password; public User(Integer uid, String username, String password) { this.uid uid; this.username username; this.password password; } Override public String toString() { return User{uid uid , username username , password password }; } }import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; public class Demo06QueryUserList { public static void main(String[] args) { String sql SELECT uid, username, password FROM user; ListUser users new ArrayList(); try ( Connection conn JDBCUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery() ) { while (rs.next()) { User user new User( rs.getInt(uid), rs.getString(username), rs.getString(password) ); users.add(user); } } catch (Exception e) { e.printStackTrace(); } System.out.println(users); } }4.5 获取最新插入数据的 id1.MySQL 函数: SELECT LAST_INSERT_ID(); 2.JDBC 更常用方式: prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) 3.前提: 主键是自增长,插入时不手动维护 id。import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; public class Demo07GeneratedKey { public static void main(String[] args) throws Exception { String sql INSERT INTO user (username, password) VALUES (?, ?); try ( Connection conn JDBCUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) ) { ps.setString(1, new_user); ps.setString(2, 123456); ps.executeUpdate(); try (ResultSet keys ps.getGeneratedKeys()) { if (keys.next()) { int id keys.getInt(1); System.out.println(新用户 id: id); } } } } }第五章.批处理5.1 批处理的介绍1.问题: 一条一条向数据库发送 SQL,效率较低。 2.批处理: 先把多组参数打包,再一次性发送给数据库执行。 3.常见场景: 批量导入数据。5.2 开启 MySQL 批处理参数urljdbc:mysql://localhost:3306/jdbc_study?useSSLfalseserverTimezoneAsia/ShanghairewriteBatchedStatementstruerewriteBatchedStatementstrue: 告诉 MySQL 驱动尽量把批量语句合并发送,提升批量插入效率。5.3 PreparedStatement 批量添加CREATE TABLE category ( cid INT PRIMARY KEY AUTO_INCREMENT, cname VARCHAR(20) );import java.sql.Connection; import java.sql.PreparedStatement; public class Demo08BatchInsert { public static void main(String[] args) throws Exception { String sql INSERT INTO category (cname) VALUES (?); try ( Connection conn JDBCUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql) ) { for (int i 1; i 1000; i) { ps.setString(1, 分类 i); ps.addBatch(); if (i % 500 0) { ps.executeBatch(); ps.clearBatch(); } } ps.executeBatch(); ps.clearBatch(); } } }方法说明addBatch()把当前参数加入批处理executeBatch()执行已打包的 SQLclearBatch()清空批处理缓存第六章.数据库连接池6.1 为什么需要连接池1.没有连接池: 每次操作数据库都创建 Connection。 用完后 close 销毁。 2.问题: 频繁创建和销毁连接非常消耗资源。 3.连接池: 提前创建一批连接。 使用时从池中获取。 用完后归还到池中。程序 - 从连接池借 Connection - 操作数据库 - close 归还连接6.2 DataSource 接口1.DataSource: Java 提供的连接池标准接口。 2.常见实现: Druid C3P0 DBCP HikariCP 3.核心方法: Connection getConnection()6.3 Druid 依赖dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency6.4 druid.propertiesdriverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/jdbc_study?useSSLfalseserverTimezoneAsia/ShanghairewriteBatchedStatementstrue usernameroot passwordroot initialSize5 maxActive10 maxWait1000配置说明initialSize初始化连接数量maxActive最大连接数量maxWait获取连接最大等待时间毫秒6.5 DruidUtils 工具类import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class DruidUtils { private static DataSource dataSource; private DruidUtils() { } static { try { Properties properties new Properties(); InputStream in DruidUtils.class.getClassLoader().getResourceAsStream(druid.properties); properties.load(in); dataSource DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { throw new RuntimeException(初始化 Druid 连接池失败, e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static DataSource getDataSource() { return dataSource; } public static void close(Connection conn, Statement st, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st ! null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }连接池中的 close使用连接池后conn.close()通常不是销毁连接而是把连接归还给连接池。6.6 使用 Druid 查询import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; public class Demo09DruidQuery { public static void main(String[] args) { String sql SELECT uid, username, password FROM user WHERE uid ?; try ( Connection conn DruidUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql) ) { ps.setInt(1, 1); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { System.out.println(rs.getString(username)); } } } catch (Exception e) { e.printStackTrace(); } } }第七章.JDBC 事务本章目标JDBC 中事务控制依赖Connection。多个 SQL 必须使用同一个连接才能放在同一个事务中。7.1 JDBC 事务方法方法说明setAutoCommit(false)关闭自动提交开启事务commit()提交事务rollback()回滚事务7.2 转账案例CREATE TABLE account ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20), balance DECIMAL(10, 2) ); INSERT INTO account (name, balance) VALUES (张三, 1000.00), (李四, 1000.00);import java.sql.Connection; import java.sql.PreparedStatement; public class Demo10Transaction { public static void main(String[] args) { String sql UPDATE account SET balance balance ? WHERE name ?; Connection conn null; try { conn DruidUtils.getConnection(); conn.setAutoCommit(false); try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setBigDecimal(1, new java.math.BigDecimal(-100)); ps.setString(2, 张三); ps.executeUpdate(); ps.setBigDecimal(1, new java.math.BigDecimal(100)); ps.setString(2, 李四); ps.executeUpdate(); } conn.commit(); } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (Exception rollbackException) { rollbackException.printStackTrace(); } } e.printStackTrace(); } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (Exception closeException) { closeException.printStackTrace(); } } } } }第八章.反射本章目标反射用于在程序运行期间获取类的信息并操作构造方法、成员方法和成员变量。后续框架会大量使用反射创建对象、注入属性、调用方法。8.1 反射的介绍1.万物皆对象: class 文件有对象 - Class 对象。 构造方法有对象 - Constructor 对象。 成员变量有对象 - Field 对象。 成员方法有对象 - Method 对象。 2.反射: 解剖 Class 对象的技术。 3.可以做什么: a.获取类的信息 b.获取构造方法并创建对象 c.获取成员变量并赋值取值 d.获取成员方法并调用8.2 获取 Class 对象方式示例对象调用getClass()person.getClass()类名.classPerson.classClass.forName()Class.forName(com.example.Person)public class Demo11ClassObject { public static void main(String[] args) throws Exception { Person person new Person(); Class? c1 person.getClass(); ClassPerson c2 Person.class; Class? c3 Class.forName(com.example.Person); System.out.println(c1 c2); System.out.println(c2 c3); } }最通用方式Class.forName(类的全限定名)最灵活因为类名可以来自配置文件。8.3 反射操作构造方法import java.lang.reflect.Constructor; public class Demo12Constructor { public static void main(String[] args) throws Exception { ClassPerson clazz Person.class; ConstructorPerson constructor clazz.getDeclaredConstructor(String.class, int.class); Person person constructor.newInstance(张三, 18); System.out.println(person); } }方法说明getDeclaredConstructors()获取所有构造包括私有getDeclaredConstructor(Class?... types)获取指定构造newInstance(Object... args)根据构造创建对象setAccessible(true)解除私有权限检查8.4 反射操作成员方法import java.lang.reflect.Method; public class Demo13Method { public static void main(String[] args) throws Exception { ClassPerson clazz Person.class; Person person clazz.getDeclaredConstructor().newInstance(); Method method clazz.getDeclaredMethod(setName, String.class); method.invoke(person, 李四); Method getName clazz.getDeclaredMethod(getName); Object result getName.invoke(person); System.out.println(result); } }方法说明getDeclaredMethods()获取全部方法包括私有getDeclaredMethod(String name, Class?... types)获取指定方法invoke(Object obj, Object... args)调用方法8.5 反射操作成员变量import java.lang.reflect.Field; public class Demo14Field { public static void main(String[] args) throws Exception { ClassPerson clazz Person.class; Person person clazz.getDeclaredConstructor().newInstance(); Field field clazz.getDeclaredField(name); field.setAccessible(true); field.set(person, 王五); Object value field.get(person); System.out.println(value); } }方法说明getDeclaredFields()获取全部字段包括私有getDeclaredField(String name)获取指定字段set(Object obj, Object value)给字段赋值get(Object obj)获取字段值8.6 反射案例:根据配置创建对象classNamecom.example.Person methodNamesayHelloimport java.io.InputStream; import java.lang.reflect.Method; import java.util.Properties; public class Demo15ReflectConfig { public static void main(String[] args) throws Exception { Properties properties new Properties(); InputStream in Demo15ReflectConfig.class.getClassLoader().getResourceAsStream(reflect.properties); properties.load(in); String className properties.getProperty(className); String methodName properties.getProperty(methodName); Class? clazz Class.forName(className); Object object clazz.getDeclaredConstructor().newInstance(); Method method clazz.getDeclaredMethod(methodName); method.invoke(object); } }这个案例体现反射的灵活性: 修改配置文件就能切换类或方法, Java 代码不用重新编译。第九章.注解9.1 注解的介绍1.注解: JDK 1.5 出现的一种引用数据类型。 2.注解和类、接口、枚举属于同一层级。 3.作用: a.说明:生成 API 文档 b.检查:检查代码是否符合规则 c.分析:让程序读取注解并执行对应逻辑9.2 JDK 常见注解注解说明Override检查方法是否为重写方法Deprecated标记方法或类已过时SuppressWarnings抑制警告FunctionalInterface检查接口是否为函数式接口public class Person { Deprecated public void eat() { System.out.println(吃饭); } }SuppressWarnings(all) public class Demo16Annotation { public static void main(String[] args) { java.util.ArrayList list new java.util.ArrayList(); list.add(abc); } }9.3 自定义注解public interface Book { String bookName(); String[] author(); int price(); int count() default 10; }1.定义格式: public interface 注解名 {} 2.注解属性: 数据类型 属性名(); 数据类型 属性名() default 默认值; 3.属性支持类型: a.8 种基本类型 b.String c.Class d.枚举 e.注解 f.以上类型的一维数组9.4 使用注解Book(bookName Java 基础, author {小周, 小王}, price 99) public class BookShelf { }注解使用注意: 1.空注解可以直接使用。 2.没有默认值的属性必须赋值。 3.多个属性之间使用逗号分隔。 4.数组属性使用 {}。 5.如果属性名叫 value,并且只有这一个属性,使用时可以省略属性名。public interface Table { String value(); }Table(user) public class User { }9.5 元注解1.元注解: 管理注解的注解。 2.常用元注解: Target Retention元注解作用Target控制注解可以写在哪些位置Retention控制注解保留到哪个阶段ElementType 常用值值说明TYPE类、接口、枚举上FIELD成员变量上METHOD方法上PARAMETER参数上CONSTRUCTOR构造方法上RetentionPolicy 常用值值说明SOURCE只保留在源码中CLASS保留到 class 文件中RUNTIME运行时仍然存在可以被反射解析import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; Target({ElementType.TYPE, ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) public interface Book { String bookName(); String[] author(); int price(); int count() default 10; }9.6 注解解析1.注解解析: 使用反射读取注解中的属性值。 2.相关接口: AnnotatedElement 3.常用实现: Class Constructor Field Method方法说明isAnnotationPresent(Class annotationClass)判断是否存在指定注解getAnnotation(Class annotationClass)获取指定注解对象Book(bookName Java 基础, author {小周}, price 99) public class BookShelf { }public class Demo17ParseAnnotation { public static void main(String[] args) { ClassBookShelf clazz BookShelf.class; boolean present clazz.isAnnotationPresent(Book.class); if (present) { Book book clazz.getAnnotation(Book.class); System.out.println(book.bookName()); System.out.println(java.util.Arrays.toString(book.author())); System.out.println(book.price()); System.out.println(book.count()); } } }9.7 注解和 JDBC 的简单联系后续框架中常见思路: 1.在类上使用注解标记表名。 2.在属性上使用注解标记字段名。 3.通过反射读取类、属性和注解。 4.自动生成 SQL 或自动封装对象。Table(user) public class User { private Integer uid; private String username; private String password; }