diff --git a/JDBC/01 JDBC简介.md b/JDBC/01 JDBC简介.md new file mode 100644 index 00000000..98f05bc7 --- /dev/null +++ b/JDBC/01 JDBC简介.md @@ -0,0 +1,47 @@ + +## 1. 简介 + +### 概述 + +各个数据库厂商去实现这套接口,提供数据库驱动jar包 +我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类 + +各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发。可随时替换底层数据库,访问数据库的Java代码基本不变。以后编写操作数据库的代码只需要面向JDBC(接口),操作哪儿个关系型数据库就需要导入该数据库的驱动包,如需要操作MySQL数据库,就需要再项目中导入MySQL数据库的驱动包。 + + +### jdbc的架构 +![](image/2023-11-25-12-05-29.png) + + +![](image/2023-11-25-12-06-14.png) + +### 数据库操作的层次 +1. 准备一个数据库:mysql +2. JDBC数据库连接:JDBC定义了数据库操作的规范,数据库驱动实现了数据库操作的规范。 +3. DataSource数据源/连接池。舔奶盖理论,当创建和销毁连接消耗了大量的性能,引进池化技术,创建连接池只释放连接、不销毁连接,实现对连接的复用。 +4. 数据库操作框架:SpringData、Mybatis。提供了对JDBC基本数据库操作的封装。 + +## 2. 核心类 +![](image/2023-11-26-17-21-49.png) + + +### DriverManager +1. 将第三方尝试实现的驱动jar注册到程序中 +2. 可以根据数据库链接信息获取connection + +### Connection +1. 和数据库建立链接,在此基础上可以进行多次CRUD操作 +2. 可以获取statement/preparestatement/callablestatement对象 + + +### Statement +1. Statement :用于执行静态 SQL 语句并返回它所生成结果的对象。 +2. PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。 +2. CallableStatement:用于执行 SQL 存储过程 + +发送sql语句到数据库软件, +不同statement封装了不同的发送方式 + +### Result + +封装了返回的结果。 \ No newline at end of file diff --git a/JDBC/02 JDBC使用.md b/JDBC/02 JDBC使用.md new file mode 100644 index 00000000..b13422dd --- /dev/null +++ b/JDBC/02 JDBC使用.md @@ -0,0 +1,332 @@ +## 基本步骤 + +![](image/2023-11-25-12-35-19.png) + +### 1.注册驱动 +1. 准备数据库的建表语句 + +```sql +CREATE TABLE user ( + id INT AUTO_INCREMENT, + name VARCHAR(100), + PRIMARY KEY(id) +); +``` +2. 通过反射机制+静态代码块初始化数据库驱动。 + 1. 通过反射的方式读取文件中的数据库驱动类路径,,方便外部化配置 + 2. 使用静态代码块,在类加载的时候数据库初始化驱动只需要初始化一次,实现数据库驱动的初始化。 + +```java +//反射的方式 +try { + Class.forName("com.mysql.jdbc.Driver"); +} catch (ClassNotFoundException e) { + e.printStackTrace(); +} + +//在无惨构造方法中通过静态代码块初始化了唯一的数据库驱动。 +public class Driver extends NonRegisteringDriver implements java.sql.Driver { + public Driver() throws SQLException { + } + + static { + try { + DriverManager.registerDriver(new Driver()); + } catch (SQLException var1) { + throw new RuntimeException("Can't register driver!"); + } + } +} +``` + + +### 2.获取连接 + +Connection是与特定数据库连接回话的接口,使用的时候需要导包,而且必须在程序结束的时候将其关闭。getConnection方法也需要捕获SQLException异常。 + +```java +Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/exam?characterEncoding=UTF-8", "root", "admin"); +``` + +通过加载外部配置建立数据库连接的方法 + +```java + @Test + public void testConnection5() throws Exception { + //1.加载配置文件 + InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); + Properties pros = new Properties(); + pros.load(is); + + //2.读取配置信息 + String user = pros.getProperty("user"); + String password = pros.getProperty("password"); + String url = pros.getProperty("url"); + String driverClass = pros.getProperty("driverClass"); + + //3.加载驱动 + Class.forName(driverClass); + //4.获取连接 + Connection conn = DriverManager.getConnection(url,user,password); + + System.out.println(conn); + } +``` + +其中配置文件如下 +```java +user=root +password=abc123 +url=jdbc:mysql://localhost:3306/test +driverClass=com.mysql.jdbc.Driver +``` + +### 3.创建发送sql语句对象 +创建Statement或者PreparedStatement接口,执行SQL语句 + +#### 使用Statement接口 + +Statement接口创建之后,可以执行SQL语句,完成对数据库的增删改查。其中 ,增删改只需要改变SQL语句的内容就能完成,然而查询略显复杂。在Statement中使用字符串拼接的方式,该方式存在句法复杂,容易犯错等缺点,具体在下文中的对比中介绍。所以Statement在实际过程中使用的非常的少,所以具体的放到PreparedStatement那里给出详细代码。 +1. 字符串拼接方式的SQL语句是非常繁琐的,中间有很多的单引号和双引号的混用,极易出错。 +2. 使用statement直接拼装出最终的sql查询语句容易产生sql注入。以下是sql注入的一个典型例子.SQL 注入 是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1' ) ,从而利用系统的 SQL 引擎完成恶意行为的做法 +3. Statement 实现批量插入时,效率较低 +```java +public class StatementTest { + // 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题 + @Test + public void testLogin() { + Scanner scan = new Scanner(System.in); + + System.out.print("用户名:"); + String userName = scan.nextLine(); + System.out.print("密 码:"); + String password = scan.nextLine(); + + // SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1'; + String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password + + "'"; + User user = get(sql, User.class); + if (user != null) { + System.out.println("登陆成功!"); + } else { + System.out.println("用户名或密码错误!"); + } + } + + // 使用Statement实现对数据表的查询操作 + public T get(String sql, Class clazz) { + T t = null; + Connection conn = null; + Statement st = null; + ResultSet rs = null; + try { + // 1.加载配置文件 + InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); + Properties pros = new Properties(); + pros.load(is); + + // 2.读取配置信息 + String user = pros.getProperty("user"); + String password = pros.getProperty("password"); + String url = pros.getProperty("url"); + String driverClass = pros.getProperty("driverClass"); + + // 3.加载驱动 + Class.forName(driverClass); + // 4.获取连接 + conn = DriverManager.getConnection(url, user, password); + st = conn.createStatement(); + rs = st.executeQuery(sql); + + // 获取结果集的元数据 + ResultSetMetaData rsmd = rs.getMetaData(); + + // 获取结果集的列数 + int columnCount = rsmd.getColumnCount(); + + if (rs.next()) { + t = clazz.newInstance(); + for (int i = 0; i < columnCount; i++) { + // //1. 获取列的名称 + // String columnName = rsmd.getColumnName(i+1); + // 1. 获取列的别名 + String columnName = rsmd.getColumnLabel(i + 1); + + // 2. 根据列名获取对应数据表中的数据 + Object columnVal = rs.getObject(columnName); + + // 3. 将数据表中得到的数据,封装进对象 + Field field = clazz.getDeclaredField(columnName); + field.setAccessible(true); + field.set(t, columnVal); + } + return t; + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + // 关闭资源 rs + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + // 关闭资源 st + if (st != null) { + try { + st.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + // 关闭资源 conn + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + return null; + } +} +``` + + +#### 使用PreparedStatement接口(常用) +与 Statement一样,PreparedStatement也是用来执行sql语句的与创建Statement不同的是,需要根据sql语句创建PreparedStatement。除此之外,还能够通过设置参数,指定相应的值,而不是Statement那样使用字符串拼接。 + +可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象 + +PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句 + +PreparedStatement 对象所代表的 SQL 语句中的参数用问号( ? )来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数 + +setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值 + +优势: +1. 能够通过检验条件数量防止sql注入。PreparedStatement 可以防止 SQL 注入,解决拼串问题 +2. 避免了直接拼装字符串的繁琐。代码的可读性和可维护性。 +3. PreparedStatement 能最大可能提高性能: + 1. DBServer 会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以 语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行 + 2. 在 Statement 语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样 每执行一次都要对传入的语句编译一次(语法检查,语义检查,翻译成二进制命令,缓存) + + +```java +增删改操作DML + //通用的增、删、改操作(体现一:增、删、改 ; 体现二:针对于不同的表) + public void update(String sql,Object ... args){ + Connection conn = null; + PreparedStatement ps = null; + try { + //1.获取数据库的连接 + conn = JDBCUtils.getConnection(); + //2.获取PreparedStatement的实例 (或:预编译sql语句) + ps = conn.prepareStatement(sql); + //3.填充占位符 + for(int i = 0;i < args.length;i++){ + ps.setObject(i + 1, args[i]); + } + //4.执行sql语句 + ps.execute(); + } catch (Exception e) { + e.printStackTrace(); + }finally{ + //5.关闭资源 + JDBCUtils.closeResource(conn, ps); + } + } + +查询操作DQL +// 通用的针对于不同表的查询:返回一个对象 (version 1.0) + public T getInstance(Class clazz, String sql, Object... args) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + // 1.获取数据库连接 + conn = JDBCUtils.getConnection(); + // 2.预编译sql语句,得到PreparedStatement对象 + ps = conn.prepareStatement(sql); + // 3.填充占位符 + for (int i = 0; i < args.length; i++) { + ps.setObject(i + 1, args[i]); + } + // 4.执行executeQuery(),得到结果集:ResultSet + rs = ps.executeQuery(); + // 5.得到结果集的元数据:ResultSetMetaData + ResultSetMetaData rsmd = rs.getMetaData(); + // 6.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值 + int columnCount = rsmd.getColumnCount(); + if (rs.next()) { + T t = clazz.newInstance(); + for (int i = 0; i < columnCount; i++) {// 遍历每一个列 + // 获取列值 + Object columnVal = rs.getObject(i + 1); + // 获取列的别名:列的别名,使用类的属性名充当 + String columnLabel = rsmd.getColumnLabel(i + 1); + // 6.2使用反射,给对象的相应属性赋值 + Field field = clazz.getDeclaredField(columnLabel); + field.setAccessible(true); + field.set(t, columnVal); + } + return t; + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + // 7.关闭资源 + JDBCUtils.closeResource(conn, ps, rs); + } + return null; + } +``` + +### 4.发送sql语句并获取返回结果 + +#### ResultSet +1. 查询需要调用 PreparedStatement 的 executeQuery() 方法,查询结果是一个ResultSet 对象 +2. ResultSet 对象以 逻辑表格的形式 封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现 +3. ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面 +4. ResultSet 对象维护了一个指向当前数据行的 游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行。调用 next() 方法检测下一行是否有效。 +5. 若有效,该方法返回 true,且指针下移。相当于 Iterator 对象的 hasNext() 和 next() 方法的结合体 +6. 当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值。例如: getInt(1) , getString("name") + + +```java +stmt.executeUpdate(sql); +``` +![](image/2023-11-26-17-26-05.png) + +#### ResultSetMetaData +可用于获取关于 ResultSet 对象中列的类型和属性信息的对象 ( 结果集数据的元数据 ) + +ResultSetMetaData meta = rs.getMetaData(); + +getColumnName(int column):获取指定列的 名称 + +getColumnLabel(int column):获取指定列的 别名 + +getColumnCount():返回当前 ResultSet 对象中的列数 + +getColumnTypeName(int column):检索指定列的数据库特定的类型名称 + +getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位 + +isNullable(int column):指示指定列中的值是否可以为 null + +isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的 + +针对于 表的字段名与类的属性名不相同 的情况: +1. 必须在声明 sql 时,使用类的属性名来命名字段的别名 +2. 使用 ResultSetMetaData 时,需要使用 getColumnLabel() 来替换getColumnName(), 获取列的别名。如果 sql 中没有给字段其别名,getColumnLabel() 获取的就是列名 + +### 5.资源关闭 + +释放 ResultSet, Statement, Connection。 +数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果 Connection 不能及时正确的关闭将导致系统宕机 +Connection 的使用原则是 尽量晚创建,尽量早的释放 +可以在 finally 中关闭,保证即使其他代码出现异常,资源也一定能被关闭 diff --git a/JDBC/03 批量操作.md b/JDBC/03 批量操作.md new file mode 100644 index 00000000..6db08073 --- /dev/null +++ b/JDBC/03 批量操作.md @@ -0,0 +1,149 @@ +## 1 使用方法 + +### 批量执行SQL语句 + +批量执行 SQL 语句 +当需要成批插入或者更新记录时,可以采用 Java 的 批量 更新 机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率 + +JDBC 的批量处理语句包括下面三个方法: + +* addBatch(String):添加需要批量处理的 SQL 语句或是参数; +* executeBatch():执行批量处理语句; +* clearBatch(): 清空缓存的数据 + +通常我们会遇到两种批量执行 SQL 语句的情况: + +* 多条 SQL 语句的批量处理; +* 一个 SQL 语句的批量传参; + + +### 使用实例 + + +向数据表中插入20000条数据 +#### 实现层次一:使用 Statement + +```java +Connection conn = JDBCUtils.getConnection(); +Statement st = conn.createStatement(); +for(int i = 1;i <= 20000;i++){ + String sql = "insert into goods(name) values('name_' + "+ i +")"; + st.executeUpdate(sql); +} +``` + +#### 实现层次二:使用 PreparedStatement + +```java +long start = System.currentTimeMillis(); + +Connection conn = JDBCUtils.getConnection(); + +String sql = "insert into goods(name)values(?)"; +PreparedStatement ps = conn.prepareStatement(sql); +for(int i = 1;i <= 20000;i++){ + ps.setString(1, "name_" + i); + ps.executeUpdate(); +} + +long end = System.currentTimeMillis(); +System.out.println("花费的时间为:" + (end - start));//82340 + +JDBCUtils.closeResource(conn, ps); +``` + +#### 实现层次三 +```java + +/* + * 修改1: 使用 addBatch() / executeBatch() / clearBatch() + * 修改2:mysql 服务器默认是关闭批处理的,我们需要通过一个参数,让 mysql 开启批处理的支持。 + * ?rewriteBatchedStatements=true 写在配置文件的 url 后面 + * 修改3:使用更新的 mysql 驱动:mysql-connector-java-5.1.37-bin.jar + * + */ +@Test +public void testInsert1() throws Exception{ + long start = System.currentTimeMillis(); + + Connection conn = JDBCUtils.getConnection(); + + String sql = "insert into goods(name)values(?)"; + PreparedStatement ps = conn.prepareStatement(sql); + + for(int i = 1;i <= 1000000;i++){ + ps.setString(1, "name_" + i); + //1.“攒”sql + ps.addBatch(); + if(i % 500 == 0){ + //2.执行 + ps.executeBatch(); + //3.清空 + ps.clearBatch(); + } + } + long end = System.currentTimeMillis(); + System.out.println("花费的时间为:" + (end - start));//20000条:625 //1000000条:14733 + JDBCUtils.closeResource(conn, ps); +} +``` + +#### 实现层次四 +```java +/* +* 层次四:在层次三的基础上操作 +* 使用 Connection 的 setAutoCommit(false) / commit() +*/ +@Test +public void testInsert2() throws Exception{ + long start = System.currentTimeMillis(); + + Connection conn = JDBCUtils.getConnection(); + + //1.设置为不自动提交数据 + conn.setAutoCommit(false); + + String sql = "insert into goods(name)values(?)"; + PreparedStatement ps = conn.prepareStatement(sql); + + for(int i = 1;i <= 1000000;i++){ + ps.setString(1, "name_" + i); + + //1.“攒”sql + ps.addBatch(); + if(i % 500 == 0){ + //2.执行 + ps.executeBatch(); + //3.清空 + ps.clearBatch(); + } + } + + //2.提交数据 + conn.commit(); + + long end = System.currentTimeMillis(); + System.out.println("花费的时间为:" + (end - start));//1000000条:4978 + + JDBCUtils.closeResource(conn, ps); +} +``` + +#### 其中的优化点 +一个 SQL 语句的批量传参: + +优化1: + +使用 PreparedStatement 替代 Statement + +优化2: + +使用 addBatch() / executeBatch() / clearBatch() +?rewriteBatchedStatements=true&useServerPrepStmts=false +使用更新的 mysql 驱动:mysql-connector-java-5.1.37-bin.jar +优化3: + +Connection 的 setAutoCommit(false) / commit() + + + diff --git a/JDBC/04 事务操作.md b/JDBC/04 事务操作.md new file mode 100644 index 00000000..2af700e6 --- /dev/null +++ b/JDBC/04 事务操作.md @@ -0,0 +1,143 @@ +## 1 事务操作 + +### 数据库事务介绍 +事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。 + +事务处理(事务操作): 保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都 被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务 回滚(rollback) 到最初状态。 + +为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。 + +### JDBC 事务处理 +数据一旦提交,就不可回滚。 + +数据什么时候意味着提交? + +当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。 +关闭数据库连接,数据就会自动的提交。 如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。 +JDBC 程序中为了让多个 SQL 语句作为一个事务执行: + +调用 Connection 对象的 setAutoCommit(false); ——以取消自动提交事务 +在所有的 SQL 语句都成功执行后,调用 commit(); ——以提交事务 +在出现异常时,调用 rollback(); 方法回滚事务 +若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true) +尤其是在使用数据库连接池技术时,执行 close() 方法前,建议恢复自动提交状态 + +那些操作会导致数据的自动提交? + +1、DDL 操作一旦执行,都会自动提交 + +set autocommit = false 对 DDL 操作无效 + +2、DML 操作在默认情况下,一旦执行,就会自动提交 + +可以通过 set autocommit=false 的方式取消 DML 操作的自动提交 + +3、默认在关闭连接时,会自动的提交数据 + +### 事务的ACID属性 +原子性(Atomicity) +原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 + +一致性(Consistency) +事务必须使数据库从一个一致性状态变换到另外一个一致性状态。 + +隔离性(Isolation) +事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 + +持久性(Durability) +持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。 + +## 2 数据库事务使用的过程: + + +```java +public void testJDBCTransaction(){ + Connextion conn = null; + try{ + // 1、获取数据库的连接 + conn = JDBCUtils.getConnection(); + // 2、开启事务 + conn.setAutoCommit(false); + // 3、进行数据库操作 + // ... + // 4、如果没有异常,则提交事务 + conn.commit(); + }catch(Exception e){ + e.printStackTrace(); + // 5、如果有异常,则回滚事务 + try{ + conn.rollback(); + }catch(SQLExcepiton e1){ + e1.printStackTrace(); + } + }finally{ + JDBCUtils.close(null,null,conn); + } +} +``` + +### 用户AA向用户BB转账100 + + +```java + +public void testJDBCTransaction() { + Connection conn = null; + try { + // 1.获取数据库连接 + conn = JDBCUtils.getConnection(); + // 2.开启事务 + conn.setAutoCommit(false); + // 3.进行数据库操作 + String sql1 = "update user_table set balance = balance - 100 where user = ?"; + update(conn, sql1, "AA"); + + // 模拟网络异常 + //System.out.println(10 / 0); + + String sql2 = "update user_table set balance = balance + 100 where user = ?"; + update(conn, sql2, "BB"); + // 4.若没有异常,则提交事务 + conn.commit(); + } catch (Exception e) { + e.printStackTrace(); + // 5.若有异常,则回滚事务 + try { + conn.rollback(); + } catch (SQLException e1) { + e1.printStackTrace(); + } + } finally { + try { + //6.恢复每次 DML 操作的自动提交功能 + conn.setAutoCommit(true); + } catch (SQLException e) { + e.printStackTrace(); + } + //7.关闭连接 + JDBCUtils.closeResource(conn, null, null); + } +} + +其中,对数据库操作的方法为: + +//使用事务以后的通用的增删改操作(version 2.0) +public void update(Connection conn ,String sql, Object... args) { + PreparedStatement ps = null; + try { + // 1.获取PreparedStatement的实例 (或:预编译sql语句) + ps = conn.prepareStatement(sql); + // 2.填充占位符 + for (int i = 0; i < args.length; i++) { + ps.setObject(i + 1, args[i]); + } + // 3.执行sql语句 + ps.execute(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + // 4.关闭资源 + JDBCUtils.closeResource(null, ps); + } +} +``` \ No newline at end of file diff --git a/JDBC/05 连接池.md b/JDBC/05 连接池.md new file mode 100644 index 00000000..d7939947 --- /dev/null +++ b/JDBC/05 连接池.md @@ -0,0 +1,199 @@ + +## 1 简介 + +### JDBC数据库连接池的必要性 +在使用开发基于数据库的 web 程序时,传统的模式基本是按以下步骤: + +1. 在主程序(如 servlet、beans )中建立数据库连接 +1. 进行 sql 操作 +1. 断开数据库连接 +1. 这种模式开发,存在的问题: + +普通的 JDBC 数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。 若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。 + +对于每一次数据库连接,使用完后都得断开。 否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为 Java 的内存泄漏?) +这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。 +### 数据库连接池技术 +为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。 + +数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从 “缓冲池” 中取出一个,使用完毕之后再放回去。 + +数据库连接池 负责 分配、管理和释放数据库连接,它 允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。 + +数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由 最小数据库连接数来设定 的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。 + +连接池的 最大数据库连接数量 限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。 + + +### 数据库连接池技术的优点 + +1. 资源重用 + +由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。 + +2. 更快的系统反应速度 + +数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间 + +3. 新的资源分配手段 + +对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源 + +4. 统一的连接管理,避免数据库连接泄漏 + +在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露 + + +### 基本原理 +池 == 舔奶盖。消除创建销毁对象的开销。 + +![](image/2023-11-26-11-28-20.png) + +![](image/2023-11-26-14-00-50.png) + + +### 多种开源的数据库连接池 +JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器 (Weblogic, WebSphere, Tomcat) 提供实现,也有一些开源组织提供实现: + +1. DBCP 是 Apache 提供的数据库连接池。tomcat 服务器自带 dbcp 数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。 +1. C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。 hibernate官方推荐使用 +1. Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点 +1. BoneCP 是一个开源组织提供的数据库连接池,速度快 +1. Druid 是阿里提供的数据库连接池,据说是集 DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有 BoneCP 快 + +DataSource 通常被称为数据源,它包含 连接池和连接池管理 两个部分,习惯上也经常把 DataSource 称为连接池 + +DataSource 用来取代 DriverManager 来获取 Connection,获取速度快,同时可以大幅度提高数据库访问速度。 + +特别注意: +* 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此 整个应用只需要一个数据源即可。 +* 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但 conn.close() 并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。 + + +## 2 实践 + +### C3P0数据库连接池 +获取连接方式一 +```java + +//使用C3P0数据库连接池的方式,获取数据库的连接:不推荐 +public static Connection getConnection1() throws Exception{ + ComboPooledDataSource cpds = new ComboPooledDataSource(); + cpds.setDriverClass("com.mysql.jdbc.Driver"); + cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test"); + cpds.setUser("root"); + cpds.setPassword("abc123"); + +// cpds.setMaxPoolSize(100); + + Connection conn = cpds.getConnection(); + return conn; +} +``` + +获取连接方式二 + +```java +//使用C3P0数据库连接池的配置文件方式,获取数据库的连接:推荐 +private static DataSource cpds = new ComboPooledDataSource("helloc3p0"); +public static Connection getConnection2() throws SQLException{ + Connection conn = cpds.getConnection(); + return conn; +} +``` + +其中,src 下的配置文件为:【c3p0-config.xml】 + +```xml + + + + + + root + abc123 + jdbc:mysql:///test + com.mysql.jdbc.Driver + + + + 5 + + 5 + + 5 + + 10 + + 20 + + 5 + + + +``` + +### Druid(德鲁伊)数据库连接池 +Druid 是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool 等 DB 池的优点,同时加入了日志监控,可以很好的监控 DB 池连接和 SQL 的执行情况,可以说是针对监控而生的 DB 连接池,可以说是目前最好的连接池之一。 + +```java +package com.atguigu.druid; + +import java.sql.Connection; +import java.util.Properties; +import javax.sql.DataSource; +import com.alibaba.druid.pool.DruidDataSourceFactory; + +public class TestDruid { + public static void main(String[] args) throws Exception { + Properties pro = new Properties(); + pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties")); + DataSource ds = DruidDataSourceFactory.createDataSource(pro); + Connection conn = ds.getConnection(); + System.out.println(conn); + } +} +``` + +其中,src下的配置文件为:【druid.properties】 +```sh + +url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true +username=root +password=123456 +driverClassName=com.mysql.jdbc.Driver + +initialSize=10 +maxActive=20 +maxWait=1000 +filters=wall +``` + +详细配置参数: + + +| 配置 | 缺省 | 说明 | +|-------------------------------|----------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| +| name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) | +| url | 连接数据库的 url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | +| username | 连接数据库的用户名 | +| password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用 ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter | +| driverClassName | 根据 url 自动识别 这一项可配可不配,如果不配置 druid 会根据url自动识别 dbType,然后选择相应的driverClassName(建议配置下) | +| initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时 | +| maxActive | 8 | 最大连接池数量 | +| maxIdle | 8 | 已经不再使用,配置了也没效果 | +| minIdle | 最小连接池数量 | +| maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。 | +| poolPreparedStatements | false | 是否缓存 PreparedStatement,也就是 PSCache。PSCache 对支持游标的数据库性能提升巨大,比如说 oracle。在 mysql 下建议关闭。 | +| maxOpenPreparedStatements | -1 | 要启用 PSCache,必须配置大于 0,当大于 0 时,poolPreparedStatements 自动触发修改为 true。在 Druid 中,不会存在 Oracle 下 PSCache 占用内存过多的问题,可以把这个数值配置大一些,比如说 100 | +| validationQuery | 用来检测连接是否有效的 sql,要求是一个查询语句。如果 validationQuery 为 null,testOnBorrow、testOnReturn、testWhileIdle 都不会其作用。 | +| testOnBorrow | true | 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。 | +| testOnReturn | false | 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能 | +| testWhileIdle | false | 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。 | +| timeBetweenEvictionRunsMillis | 有两个含义: 1) Destroy 线程会检测连接的间隔时间 ; 2) testWhileIdle 的判断依据,详细看 testWhileIdle 属性的说明 | +| numTestsPerEvictionRun | 不再使用,一个 DruidDataSource 只支持一个 EvictionRun | +| minEvictableIdleTimeMillis | +| connectionInitSqls | 物理连接初始化的时候执行的 sql | +| exceptionSorter | 根据 dbType 自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 | +| filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的 filter:stat 日志用的filter:log4j防御sql注入的filter:wall | +| proxyFilters | 类型是 List,如果同时配置了 filters 和 proxyFilters,是组合关系,并非替换关系 | diff --git a/JDBC/06 操作封装.md b/JDBC/06 操作封装.md new file mode 100644 index 00000000..d2a27a55 --- /dev/null +++ b/JDBC/06 操作封装.md @@ -0,0 +1,589 @@ + +## 1 泛型方法封装 + +### JDBCUtil反射+线程变量实现数据库连接和事务控制 + +1. 单例化datasource。无需重复创建 +2. 使用property从配置文件中加载配置 +3. 创建druid线程池复用连接,提升查询效率。 +4. 利用线程变量获取连接信息,确保一个线程中的多个方法可以获取同一个connection + + +```java +package org.example.dao; + +import com.alibaba.druid.pool.DruidDataSourceFactory; + +import javax.sql.DataSource; +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +public class JDBCUtils { + static DataSource datasource = null; + + private static ThreadLocal tl = new ThreadLocal<>(); + + static { + Properties properties = new Properties(); + + InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"); + try{ + properties.load(in); + }catch(IOException e){ + throw new RuntimeException(e); + } + + try { + datasource = DruidDataSourceFactory.createDataSource(properties); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + public static Connection getConnection() throws SQLException { + Connection connection = tl.get(); + if(connection == null){ + connection = datasource.getConnection(); + tl.set(connection); + } + return connection; + } + + public static void freeConnection() throws SQLException { + + Connection connection = tl.get(); + if(connection != null){ + tl.remove(); + connection.setAutoCommit(false); + connection.close(); + } + } +} +``` + + + +### BaseDAO反射+泛型实现查询结果解析 +```java +package org.example.dao; + +import java.lang.reflect.Field; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +public class BaseDAO { + + public int executeUpdate(String sql, Object... args) throws SQLException { + Connection conn = JDBCUtils.getConnection(); + + PreparedStatement ps = conn.prepareStatement(sql); + + for (int i = 0; i < args.length; i++) { + ps.setString(i, args[i-1].toString()); + } + + int rows = ps.executeUpdate(); + ps.close(); + if (!conn.getAutoCommit()) { + conn.close(); + } + return rows; + } + + public List executeQuery(Class clazz,String sql, Object... args) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException { + Connection conn = JDBCUtils.getConnection(); + + PreparedStatement ps = conn.prepareStatement(sql); + + for (int i = 0; i < args.length; i++) { + ps.setString(i, args[i-1].toString()); + } + + ResultSet resultSet = ps.executeQuery(); + + //对返回值进行解析 + ResultSetMetaData resultMeta = resultSet.getMetaData(); + int count = resultMeta.getColumnCount(); + + List rows = new ArrayList(); + while(resultSet.next()){ + T t = clazz.newInstance(); + for (int i = 1; i < count; i++) { + Object object = resultSet.getObject(i); + String columnName = resultMeta.getColumnName(i); + Field field = clazz.getDeclaredField(columnName); + field.setAccessible(true); + field.set(t,object); + } + rows.add(t); + } + + + + ps.close(); + if (!conn.getAutoCommit()) { + conn.close(); + } + return rows; + } +} + +``` + + +## 2 BaseDAO泛型类 +### 典型的BaseDAO的封装过程 + +1. 提供类级别的泛型,在初始化的方法中可以通过this.getClass来确定泛型参数。 +```java +package com.atguigu.bookstore.dao; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +import org.apache.commons.dbutils.QueryRunner; +import org.apache.commons.dbutils.handlers.BeanHandler; +import org.apache.commons.dbutils.handlers.BeanListHandler; +import org.apache.commons.dbutils.handlers.ScalarHandler; + +/** + * 定义一个用来被继承的对数据库进行基本操作的 Dao + * @param + */ +public abstract class BaseDao { + private QueryRunner queryRunner = new QueryRunner(); + // 定义一个变量来接收泛型的类型 + private Class type; + + // 获取T的Class对象,获取泛型的类型,泛型是在被子类继承时才确定 + public BaseDao() { + // 获取子类的类型 + Class clazz = this.getClass(); + // 获取父类的类型 + // getGenericSuperclass()用来获取当前类的父类的类型 + // ParameterizedType表示的是带泛型的类型 + ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass(); + // 获取具体的泛型类型 getActualTypeArguments获取具体的泛型的类型 + // 这个方法会返回一个Type的数组 + Type[] types = parameterizedType.getActualTypeArguments(); + // 获取具体的泛型的类型· + this.type = (Class) types[0]; + } + + /** + * 通用的增删改操作 + * + * @param sql + * @param params + * @return + */ + public int update(Connection conn,String sql, Object... params) { + int count = 0; + try { + count = queryRunner.update(conn, sql, params); + } catch (SQLException e) { + e.printStackTrace(); + } + return count; + } + + /** + * 获取一个对象 + * + * @param sql + * @param params + * @return + */ + public T getBean(Connection conn,String sql, Object... params) { + T t = null; + try { + t = queryRunner.query(conn, sql, new BeanHandler(type), params); + } catch (SQLException e) { + e.printStackTrace(); + } + return t; + } + + /** + * 获取所有对象 + * + * @param sql + * @param params + * @return + */ + public List getBeanList(Connection conn,String sql, Object... params) { + List list = null; + try { + list = queryRunner.query(conn, sql, new BeanListHandler(type), params); + } catch (SQLException e) { + e.printStackTrace(); + } + return list; + } + + /** + * 获取一个但一值得方法,专门用来执行像 select count(*)...这样的sql语句 + * + * @param sql + * @param params + * @return + */ + public Object getValue(Connection conn,String sql, Object... params) { + Object count = null; + try { + // 调用queryRunner的query方法获取一个单一的值 + count = queryRunner.query(conn, sql, new ScalarHandler<>(), params); + } catch (SQLException e) { + e.printStackTrace(); + } + return count; + } +} +``` + +### 使用BaseDAO实例 + +```java +package com.atguigu.bookstore.dao.impl; + +import java.sql.Connection; +import java.util.List; + +import com.atguigu.bookstore.beans.Book; +import com.atguigu.bookstore.beans.Page; +import com.atguigu.bookstore.dao.BaseDao; +import com.atguigu.bookstore.dao.BookDao; + +public class BookDaoImpl extends BaseDao implements BookDao { + + @Override + public List getBooks(Connection conn) { + // 调用BaseDao中得到一个List的方法 + List beanList = null; + // 写sql语句 + String sql = "select id,title,author,price,sales,stock,img_path imgPath from books"; + beanList = getBeanList(conn,sql); + return beanList; + } + + @Override + public void saveBook(Connection conn,Book book) { + // 写sql语句 + String sql = "insert into books(title,author,price,sales,stock,img_path) values(?,?,?,?,?,?)"; + // 调用BaseDao中通用的增删改的方法 + update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(),book.getImgPath()); + } + + @Override + public void deleteBookById(Connection conn,String bookId) { + // 写sql语句 + String sql = "DELETE FROM books WHERE id = ?"; + // 调用BaseDao中通用增删改的方法 + update(conn,sql, bookId); + + } + + @Override + public Book getBookById(Connection conn,String bookId) { + // 调用BaseDao中获取一个对象的方法 + Book book = null; + // 写sql语句 + String sql = "select id,title,author,price,sales,stock,img_path imgPath from books where id = ?"; + book = getBean(conn,sql, bookId); + return book; + } + + @Override + public void updateBook(Connection conn,Book book) { + // 写sql语句 + String sql = "update books set title = ? , author = ? , price = ? , sales = ? , stock = ? where id = ?"; + // 调用BaseDao中通用的增删改的方法 + update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getId()); + } + + @Override + public Page getPageBooks(Connection conn,Page page) { + // 获取数据库中图书的总记录数 + String sql = "select count(*) from books"; + // 调用BaseDao中获取一个单一值的方法 + long totalRecord = (long) getValue(conn,sql); + // 将总记录数设置都page对象中 + page.setTotalRecord((int) totalRecord); + + // 获取当前页中的记录存放的List + String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books limit ?,?"; + // 调用BaseDao中获取一个集合的方法 + List beanList = getBeanList(conn,sql2, (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE); + // 将这个List设置到page对象中 + page.setList(beanList); + return page; + } + + @Override + public Page getPageBooksByPrice(Connection conn,Page page, double minPrice, double maxPrice) { + // 获取数据库中图书的总记录数 + String sql = "select count(*) from books where price between ? and ?"; + // 调用BaseDao中获取一个单一值的方法 + long totalRecord = (long) getValue(conn,sql,minPrice,maxPrice); + // 将总记录数设置都page对象中 + page.setTotalRecord((int) totalRecord); + + // 获取当前页中的记录存放的List + String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books where price between ? and ? limit ?,?"; + // 调用BaseDao中获取一个集合的方法 + List beanList = getBeanList(conn,sql2, minPrice , maxPrice , (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE); + // 将这个List设置到page对象中 + page.setList(beanList); + + return page; + } +} +``` + +```java +package com.atguigu.bookstore.dao.impl; + +import java.sql.Connection; + +import com.atguigu.bookstore.beans.User; +import com.atguigu.bookstore.dao.BaseDao; +import com.atguigu.bookstore.dao.UserDao; + +public class UserDaoImpl extends BaseDao implements UserDao { + + @Override + public User getUser(Connection conn,User user) { + // 调用BaseDao中获取一个对象的方法 + User bean = null; + // 写sql语句 + String sql = "select id,username,password,email from users where username = ? and password = ?"; + bean = getBean(conn,sql, user.getUsername(), user.getPassword()); + return bean; + } + + @Override + public boolean checkUsername(Connection conn,User user) { + // 调用BaseDao中获取一个对象的方法 + User bean = null; + // 写sql语句 + String sql = "select id,username,password,email from users where username = ?"; + bean = getBean(conn,sql, user.getUsername()); + return bean != null; + } + + @Override + public void saveUser(Connection conn,User user) { + //写sql语句 + String sql = "insert into users(username,password,email) values(?,?,?)"; + //调用BaseDao中通用的增删改的方法 + update(conn,sql, user.getUsername(),user.getPassword(),user.getEmail()); + } +} +``` + + +## 3 ApachDBUtils + +### Apache-DBUtils简介 +commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用 dbutils 能极大简化 jdbc 编码的工作量,同时也不会影响程序的性能。 + +API介绍: + +org.apache.commons.dbutils.QueryRunner +org.apache.commons.dbutils.ResultSetHandler +工具类:org.apache.commons.dbutils.DbUtils + + +### 主要 API 的使用 + +#### DbUtils +DbUtils :提供如关闭连接、装载 JDBC 驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下: +* public static void close(…) throws java.sql.SQLException : DbUtils 类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是 NULL,如果不是的话,它们就关闭 Connection、Statement 和 ResultSet。 +* public static void closeQuietly(…) : 这一类方法不仅能在Connection、Statement 和 ResultSet 为 NULL 情况下避免关闭,还能隐藏一些在程序中抛出的 SQLEeception。 +* public static void commitAndClose(Connection conn)throws SQLException : 用来提交连接的事务,然后关闭连接 +* public static void commitAndCloseQuietly(Connection conn) : 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。 +* public static void rollback(Connection conn)throws SQLException :允许 conn 为 null ,因为方法内部做了判断 +* public static void rollbackAndClose(Connection conn)throws SQLException +* rollbackAndCloseQuietly(Connection) +* public static boolean loadDriver(java.lang.String driverClassName) :这一方装载并注册 JDBC 驱动程序,如果成功就返回 true。使用该方法,你不需要捕捉这个异常 ClassNotFoundException。 + + +#### QueryRunner 类 +该类简单化了 SQL 查询,它与 ResultSetHandler 组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。 + +QueryRunner 类提供了两个构造器: + +默认的构造器 +需要一个 javax.sql.DataSource 来作参数的构造器 +QueryRunner 类的主要方法: + +更新 +public int update(Connection conn, String sql, Object... params) throws SQLException : 用来执行一个更新(插入、更新或删除)操作。 +… +插入 +public T insert(Connection conn,String sql,ResultSetHandler rsh, Object... params) throws SQLException :只支持 INSERT 语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler. 即自动生成的键值 +… +批处理 +public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE 语句 +public T insertBatch(Connection conn, String sql, ResultSetHandler rsh, Object[][] params)throws SQLException:只支持 INSERT 语句 +… +查询 +public Object query(Connection conn, String sql, ResultSetHandler rsh,Object... params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。 + + +使用测试 + + +```java +// 测试添加 +@Test +public void testInsert() throws Exception { + QueryRunner runner = new QueryRunner(); + Connection conn = JDBCUtils.getConnection3(); + String sql = "insert into customers(name,email,birth)values(?,?,?)"; + int count = runner.update(conn, sql, "何成飞", "he@qq.com", "1992-09-08"); + + System.out.println("添加了" + count + "条记录"); + JDBCUtils.closeResource(conn, null); +} + + +// 测试删除 +@Test +public void testDelete() throws Exception { + QueryRunner runner = new QueryRunner(); + Connection conn = JDBCUtils.getConnection3(); + String sql = "delete from customers where id < ?"; + int count = runner.update(conn, sql,3); + + System.out.println("删除了" + count + "条记录"); + JDBCUtils.closeResource(conn, null); +} + + + +``` + + +#### ResultSetHandler 接口及实现类 +该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。 + +ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。 + +接口的主要实现类: + +* ArrayHandler:把结果集中的第一行数据转成对象数组。 +* ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到 List 中。 +* BeanHandler: 将结果集中的第一行数据封装到一个对应的 JavaBean 实例中。 +* BeanListHandler: 将结果集中的每一行数据都封装到一个对应的 JavaBean 实例中,存放到 List 里。 +* ColumnListHandler:将结果集中某一列的数据存放到 List 中。 +* KeyedHandler(name):将结果集中的每一行数据都封装到一个 Map 里,再把这些 Map 再存到一个 map 里,其 Key 为指定的key。 +* MapHandler: 将结果集中的第一行数据封装到一个 Map 里,key 是列名,value 就是对应的值。 +* MapListHandler: 将结果集中的每一行数据都封装到一个 Map 里,然后再存放到 List +* ScalarHandler: 查询单个值对象 + + +使用测试 + +```java + +/* + * 测试查询:查询一条记录 + * + * 使用ResultSetHandler的实现类:BeanHandler + */ +@Test +public void testQueryInstance() throws Exception{ + QueryRunner runner = new QueryRunner(); + Connection conn = JDBCUtils.getConnection3(); + String sql = "select id,name,email,birth from customers where id = ?"; + // + BeanHandler handler = new BeanHandler<>(Customer.class); + Customer customer = runner.query(conn, sql, handler, 23); + System.out.println(customer); + JDBCUtils.closeResource(conn, null); +} + +/* + * 测试查询:查询多条记录构成的集合 + * + * 使用ResultSetHandler的实现类:BeanListHandler + */ +@Test +public void testQueryList() throws Exception{ + QueryRunner runner = new QueryRunner(); + Connection conn = JDBCUtils.getConnection3(); + String sql = "select id,name,email,birth from customers where id < ?"; + // + BeanListHandler handler = new BeanListHandler<>(Customer.class); + List list = runner.query(conn, sql, handler, 23); + list.forEach(System.out::println); + JDBCUtils.closeResource(conn, null); +} + +/* + * 自定义 ResultSetHandler 的实现类 + */ +@Test +public void testQueryInstance1() throws Exception{ + QueryRunner runner = new QueryRunner(); + Connection conn = JDBCUtils.getConnection3(); + String sql = "select id,name,email,birth from customers where id = ?"; + + ResultSetHandler handler = new ResultSetHandler() { + @Override + public Customer handle(ResultSet rs) throws SQLException { + System.out.println("handle"); +// return new Customer(1,"Tom","tom@126.com",new Date(123323432L)); + + if(rs.next()){ + int id = rs.getInt("id"); + String name = rs.getString("name"); + String email = rs.getString("email"); + Date birth = rs.getDate("birth"); + + return new Customer(id, name, email, birth); + } + return null; + } + }; + + Customer customer = runner.query(conn, sql, handler, 23); + + System.out.println(customer); + JDBCUtils.closeResource(conn, null); +} + +/* + * 如何查询类似于最大的,最小的,平均的,总和,个数相关的数据, + * 使用ScalarHandler + * + */ +@Test +public void testQueryValue() throws Exception{ + QueryRunner runner = new QueryRunner(); + Connection conn = JDBCUtils.getConnection3(); + + //测试一: +// String sql = "select count(*) from customers where id < ?"; +// ScalarHandler handler = new ScalarHandler(); +// long count = (long) runner.query(conn, sql, handler, 20); +// System.out.println(count); + + //测试二: + String sql = "select max(birth) from customers"; + ScalarHandler handler = new ScalarHandler(); + Date birth = (Date) runner.query(conn, sql, handler); + System.out.println(birth); + + JDBCUtils.closeResource(conn, null); +} +``` \ No newline at end of file diff --git a/JDBC/image.png b/JDBC/image.png new file mode 100644 index 00000000..1e29db68 Binary files /dev/null and b/JDBC/image.png differ diff --git a/JDBC/image/2023-11-25-12-04-27.png b/JDBC/image/2023-11-25-12-04-27.png new file mode 100644 index 00000000..0c822537 Binary files /dev/null and b/JDBC/image/2023-11-25-12-04-27.png differ diff --git a/JDBC/image/2023-11-25-12-05-29.png b/JDBC/image/2023-11-25-12-05-29.png new file mode 100644 index 00000000..a7951a64 Binary files /dev/null and b/JDBC/image/2023-11-25-12-05-29.png differ diff --git a/JDBC/image/2023-11-25-12-06-14.png b/JDBC/image/2023-11-25-12-06-14.png new file mode 100644 index 00000000..fb249e47 Binary files /dev/null and b/JDBC/image/2023-11-25-12-06-14.png differ diff --git a/JDBC/image/2023-11-25-12-35-19.png b/JDBC/image/2023-11-25-12-35-19.png new file mode 100644 index 00000000..3db9f935 Binary files /dev/null and b/JDBC/image/2023-11-25-12-35-19.png differ diff --git a/JDBC/image/2023-11-26-11-28-20.png b/JDBC/image/2023-11-26-11-28-20.png new file mode 100644 index 00000000..23540b70 Binary files /dev/null and b/JDBC/image/2023-11-26-11-28-20.png differ diff --git a/JDBC/image/2023-11-26-14-00-50.png b/JDBC/image/2023-11-26-14-00-50.png new file mode 100644 index 00000000..61dcd899 Binary files /dev/null and b/JDBC/image/2023-11-26-14-00-50.png differ diff --git a/JDBC/image/2023-11-26-17-21-49.png b/JDBC/image/2023-11-26-17-21-49.png new file mode 100644 index 00000000..5109196d Binary files /dev/null and b/JDBC/image/2023-11-26-17-21-49.png differ diff --git a/JDBC/image/2023-11-26-17-26-05.png b/JDBC/image/2023-11-26-17-26-05.png new file mode 100644 index 00000000..f079f99f Binary files /dev/null and b/JDBC/image/2023-11-26-17-26-05.png differ diff --git a/Java/01Java语言基础/16 javaSPI机制.md b/Java/01Java语言基础/16 javaSPI.md similarity index 100% rename from Java/01Java语言基础/16 javaSPI机制.md rename to Java/01Java语言基础/16 javaSPI.md diff --git a/Java/01Java语言基础/17 控制灵活性.md b/Java/01Java语言基础/17 控制灵活性.md deleted file mode 100644 index 458c3850..00000000 --- a/Java/01Java语言基础/17 控制灵活性.md +++ /dev/null @@ -1,15 +0,0 @@ -## 控制的灵活性 -### 应用控制的灵活性 -控制的灵活性总共分为一下几种 -1. 代码:开发时控制。代码写死,版本固定后无法变更。 -2. 参数和环境变量:发布时控制。通过参数和环境变量,控制应用的不同表现行为 -3. 动态配置:运行时控制。通过动态配置灵活调整行为。 -4. 接口和界面:运行时控制。需要开发运行时接口和界面,控制应用的不同表现行为。 - - -### spi机制中控制的灵活性 - -在spi机制中。 -1. 编译时spi,java sdk提供的ServiceLoader机制通过反射,在编译时查找具体的实现。 -2. 启动时spi,tomcat、jdbc驱动加载、spring依赖注入都是在启动时根据当前的运行环境查找具体实现。 -3. 运行时spi,注册中心是在运行时进行服务发现的,可能在运行时进行动态变化。 \ No newline at end of file diff --git a/Java/01Java语言基础/22 Java包机制.md b/Java/01Java语言基础/22 Java包机制.md index 66d592f5..0c9158a6 100644 --- a/Java/01Java语言基础/22 Java包机制.md +++ b/Java/01Java语言基础/22 Java包机制.md @@ -1,4 +1,6 @@ -## 包 +# 包 + +## 1 基本概念 包我们每天建的项目就是在一个目录下,我们每次都会建立一个包,这个包在磁盘下其实就是一个目录。**包是用来分门别类的管理技术,不同的技术类放在不同的包下**,方便管理和维护。 **包名的命名规范**: diff --git a/Java/03Java集合类/01 集合底层结构.md b/Java/03Java集合类/01 集合底层结构.md index b13bf7d5..6a809c30 100644 --- a/Java/03Java集合类/01 集合底层结构.md +++ b/Java/03Java集合类/01 集合底层结构.md @@ -24,7 +24,7 @@ 容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。 -### Collection +### Collection单列集合

@@ -50,7 +50,7 @@ - PriorityQueue:基于堆结构实现,可以用它来实现优先队列。 -### Map +### Map双列映射

diff --git a/Java/03Java集合类/02 JavaCollection.md b/Java/03Java集合类/02 JavaCollection.md index 4cc265c7..eda3e985 100644 --- a/Java/03Java集合类/02 JavaCollection.md +++ b/Java/03Java集合类/02 JavaCollection.md @@ -19,7 +19,7 @@ C++中的容器分为(都是线性的) * queue 队列 -Java中的容器分为(都是线性的)集合collection。除了一下基本集合类型,还有多个特殊的类型,后续补充 +Java中的集合容器分为单列集合collection和双列映射Map。除了一下基本集合类型,还有多个特殊的类型,后续补充 * List * Arraylist,有序,插入序 * vector diff --git a/Java/04Java并发编程/11 Java只执行一次.md b/Java/04Java并发编程/11 Java只执行一次.md index 4cc34c72..8385e5b9 100644 --- a/Java/04Java并发编程/11 Java只执行一次.md +++ b/Java/04Java并发编程/11 Java只执行一次.md @@ -52,6 +52,7 @@ AtomicBoolean 内部持有了一个 volatile变量修饰的value, 即CAS的交换思想. AtomicBoolean 内部可以保证,在高并发情况下,同一时刻只有一个线程对变量修改成功。 +```java /** * Atomically update Java variable to x if it is currently * holding expected. @@ -61,6 +62,7 @@ public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); Unsafe.compareAndSwapInt()介绍 +``` 三、应用 1、AtomicBoolean 示例 @@ -92,7 +94,7 @@ public class AtomicBooleanDemo1 { } } 2、 使用volatile 替换 - +```java public class AtomicBooleanDemo2 { // 设置初始化值为false,通过volatile变量保证线程可见性 @@ -121,10 +123,11 @@ public class AtomicBooleanDemo2 { } } - +``` 针对这种boolean类型的并发操作,可以使用AtomicBoolean进行设置即可 三、源码分析 +```java public class AtomicBoolean implements java.io.Serializable { private static final long serialVersionUID = 4654671469794556979L; // setup to use Unsafe.compareAndSwapInt for updates @@ -155,8 +158,9 @@ public class AtomicBoolean implements java.io.Serializable { } } +``` 将expect和AtomicBoolean的value进行比较,若一致则更新为update,否则返回false - +```java /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. @@ -172,7 +176,7 @@ public class AtomicBoolean implements java.io.Serializable { // 通过unsafe的cas方法操作,比较并替换,底层通过lock前缀加锁实现原子性 return unsafe.compareAndSwapInt(this, valueOffset, e, u); } - +``` 四、unsafe方法的实现原理 1、unsafe的compareAndSet内部是如何保证原子性的? 底层通过cmpxchg汇编命令处理,如果是多处理器会使用lock前缀,可以达到内存屏障的效果,来进行隔离。 diff --git a/Java/Java学习路线.md b/Java/Java学习路线.md index 3b3c22cc..466ce619 100644 --- a/Java/Java学习路线.md +++ b/Java/Java学习路线.md @@ -4,6 +4,14 @@ 总共包括六个主要的部分。学完就能毕业啦。开始吧。 + + +对一个软件的了解程度 + +使用:快速搭建器软件,运行,实现基础的内容。(看quick start中的内容和视频即) +精通:了解每一个配置的细节,能够进行深度定制化。(看用户文档和API参考) +源码:掌握底层的原理,能进行破坏性的改进和学习。(看代码仓库源码) + ### Java 的学习路线(视频打卡系列) - [ ] 基础知识(学习方式——阅读书籍) @@ -23,12 +31,12 @@ - [ ] JDBC - [ ] lombak - [ ] mybatis -- [ ] Java 工具教程(Java 使用的关键工具,白天学习一下) +- [x] Java 工具教程(Java 使用的关键工具,白天学习一下) - [x] maven 教程 - [x] idea 教程 - [ ] Java 框架教程(Spring 全家桶,白天自学) - [x] Spring - - [ ] Springboot + - [x] Springboot - [ ] Spring MVC - [ ] SpringCloud - [ ] Java云原生 diff --git a/Java/Java核心思想.md b/Java/Java核心思想.md new file mode 100644 index 00000000..895fde71 --- /dev/null +++ b/Java/Java核心思想.md @@ -0,0 +1,89 @@ + +## 1 观察者模式 + +### 简介 + +先是一种对象间的一对多的关系;最简单的如交通信号灯,信号灯是目标(一方),行人注视着信号灯(多方)。当目标发送改变(发布),观察者(订阅者)就可以接收到改变。 观察者如何处理(如行人如何走,是快走/慢走/不走,目标不会管的), 目标无需干涉;所以就松散耦合了它们之间的关系。 + +别名:观察者模式、事件监听机制、事件通知机制、回调机制 + + +### 关键概念 +* 可观察对象、观察者。(观察者模式) +* 事件、响应。(事件驱动、事件监听机制、响应式编程) + + +### 使用场景 +观察着模式在不同场景下的实现。 +* java语言中的回调机制 +* 设计模式中的观察者模式 +* jdk中的observable和observer +* 在操作系统中就是IO多路复用的一种策略 +* 在socket网络编程、IO编程中(netty、springWebFlux、sofarpc)就是Reactor模式 +* Spring的事件驱动模型![](image/2022-10-18-12-06-37.png) +* 在UI框架中就是Listener,事件监听机制和响应机制 +* 在web网站开发中,被称为响应式编程。 +* 消息队列中的事件通知机制,消息队列中通过Event的事件传递变化。 + + + +## 2 服务发现机制 + +### 简介 + + +观察者模式是一种**传递变化的功能**。强调的是变化发生时,那些关注该变化的角色都会收到相应的通知,而采取对应的操作。 + +而服务发现机制强调的是**建立关系的过程**,强调的是一个角色B通过某种机制与角色A建立永久的关系,建立关系后角色B和角色A之间往往采取更加直接的联系,例如本地调用或者远程调用。也可能建立关系后进行间接的事件通知逻辑,例如springfactories发现spring.factories中定义的扩展接口后,实例化这些接口,然后通过事件监听机制通知这些接口的实现类发生了变化。 + +服务发现机制,本质上也是一种解耦。角色B自身的逻辑不会依赖具体的实现,可以由不同的实现方提供。 + +别名:服务发布机制、订阅发布机制 + +### 关键概念 +* 发布者、订阅者。(发布订阅机制) +* 提供者、消费者 + +### 使用场景 + +服务发现机制。以下并不是观察者模式的范畴,是一种更加宏观的机制,可能使用了观察者模式去发现。 +* javaSPI。服务提供者接口,在编译阶段基于文件对服务进行发现 +* SpringFactories。Spring的服务发现机制,通过文件发现接口的实现。 +* 在微服务中就是注册中心的发布订阅过程。发布者订阅者、提供者消费者。 +* 消息队列中的订阅发布模式,在消息队列中就是通过topic建立订阅发布关系。 + + + +## 3 扩展机制 +扩展机制。框架这些东西就是为了给别人用的,写好了核心逻辑,让用户在核心逻辑的基础上进行扩展。这也是软件设计的原则。提供代码复用。 + +依赖可以分为两种核心依赖。 +* 一种是库即工具,你自己创建核心的流程和逻辑,调用库中的方法完成一些简化的操作。 +* 一种是框架。框架一般会创建好核心的流程和逻辑,你只是在框架的扩展点上进行扩展,实现一些业务相关的细节。 + + +复用是框架的核心目的,扩展性是衡量框架好快的基本指标,扩展机制主要分为一下三个阶段 +* 发现:框架提供给了发现扩展点的方法的方法,有些是框架提供的有些是语言提供的。 + * JavaSPI机制。本地的接口注册和发现。 + * 接口机制。Java提供的设计实现了某些接口就会被发现 + * 注解机制。使用了某些给定的注解就可以视为某个扩展 +* 调用:发现扩展点后,如何讲扩展点生效。 + * 直接的调用行为。直接调用发现的服务弯沉给某些操作 + * 回调机制。在生命周期的某个阶段,回调扩展点的具体方法。 + + +## 控制的灵活性 +### 应用控制的灵活性 +控制的灵活性总共分为一下几种 +1. 代码:开发时控制。代码写死,版本固定后无法变更。 +2. 参数和环境变量:发布时控制。通过参数和环境变量,控制应用的不同表现行为 +3. 动态配置:运行时控制。通过动态配置灵活调整行为。 +4. 接口和界面:运行时控制。需要开发运行时接口和界面,控制应用的不同表现行为。 + + +### 服务发现机制中控制的灵活性 + +不同阶段的服务发现机制的灵活性 +1. 编译时服务发现机制,java sdk提供的ServiceLoader机制(SPI)通过反射,在编译时查找具体的实现。 +2. 启动时服务发现机制,tomcat、jdbc驱动加载、spring依赖注入都是在启动时根据当前的运行环境查找具体实现。 +3. 运行时服务发现机制,注册中心是在运行时进行服务发现的,可能在运行时进行动态变化。 \ No newline at end of file diff --git a/Java/总结文档.md b/Java/Java面试原理/总结文档.md similarity index 98% rename from Java/总结文档.md rename to Java/Java面试原理/总结文档.md index 6d7f7860..cf27f9c0 100644 --- a/Java/总结文档.md +++ b/Java/Java面试原理/总结文档.md @@ -1,4 +1,23 @@ ## 总结文档 + +- [总结文档](#总结文档) + - [普通集合](#普通集合) + - [并发集合](#并发集合) + - [字节流与字符流](#字节流与字符流) + - [static、final、super、this 关键字(this、super 不能用在 static 方法中)以及泛型](#staticfinalsuperthis-关键字thissuper-不能用在-static-方法中以及泛型) + - [异常体系](#异常体系) + - [ava 的 IO](#ava-的-io) + - [java 对象如何判断是否可以回收(注意,此处仅仅为判断对象是否可达,不一定判断对象是否可以回收)](#java-对象如何判断是否可以回收注意此处仅仅为判断对象是否可达不一定判断对象是否可以回收) + - [java 中的 SPI](#java-中的-spi) + - [Java 虚拟机](#java-虚拟机) + - [Java 锁机制](#java-锁机制) + - [java 多线程](#java-多线程) + - [Spring](#spring) + - [Mybatis](#mybatis) + - [Apache HttpClient](#apache-httpclient) + + + ### 普通集合 - Arrays.asList()返回的是视图(ArrayList 内部类对象,只提供了替换数据的方法,其底层依旧是原数组数据) diff --git a/Spring/Spring5/image/2022-10-18-12-06-37.png b/Java/image/2022-10-18-12-06-37.png similarity index 100% rename from Spring/Spring5/image/2022-10-18-12-06-37.png rename to Java/image/2022-10-18-12-06-37.png diff --git a/Python/androguard/apk/1.apk b/Python/androguard/apk/1.apk deleted file mode 100644 index 2d9ea969..00000000 Binary files a/Python/androguard/apk/1.apk and /dev/null differ diff --git a/Spring/SourceCode/SpringBootSourceCode/pom.xml b/Spring/SourceCode/SpringBootSourceCode/pom.xml index 5283730d..08256f7c 100644 --- a/Spring/SourceCode/SpringBootSourceCode/pom.xml +++ b/Spring/SourceCode/SpringBootSourceCode/pom.xml @@ -50,6 +50,19 @@ org.apache.commons commons-lang3 + + + mysql + mysql-connector-java + 8.0.33 + + + + com.alibaba + druid + 1.2.16 + + diff --git a/Spring/SourceCode/SpringBootSourceCode/src/main/java/org/example/controller/IndexController.java b/Spring/SourceCode/SpringBootSourceCode/src/main/java/org/example/controller/IndexController.java index e94f9f59..0d9de7a3 100644 --- a/Spring/SourceCode/SpringBootSourceCode/src/main/java/org/example/controller/IndexController.java +++ b/Spring/SourceCode/SpringBootSourceCode/src/main/java/org/example/controller/IndexController.java @@ -1,7 +1,7 @@ package org.example.controller; -import org.apache.commons.lang3.StringUtils; import org.example.bean.User; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; diff --git a/Spring/SourceCode/SpringBootSourceCode/src/main/java/org/example/interceptor/LoginInterceptor.java b/Spring/SourceCode/SpringBootSourceCode/src/main/java/org/example/interceptor/LoginInterceptor.java index 1270ecd1..502cad1e 100644 --- a/Spring/SourceCode/SpringBootSourceCode/src/main/java/org/example/interceptor/LoginInterceptor.java +++ b/Spring/SourceCode/SpringBootSourceCode/src/main/java/org/example/interceptor/LoginInterceptor.java @@ -1,10 +1,7 @@ package org.example.interceptor; import lombok.extern.slf4j.Slf4j; -import org.example.bean.User; -import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; -import org.springframework.stereotype.Controller; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; diff --git a/Spring/SourceCode/SpringBootSourceCode/src/main/resources/application.properties b/Spring/SourceCode/SpringBootSourceCode/src/main/resources/application.properties new file mode 100644 index 00000000..e69de29b diff --git a/Spring/SourceCode/SpringBootSourceCode/src/test/java/org/example/dao/BaseDAO.java b/Spring/SourceCode/SpringBootSourceCode/src/test/java/org/example/dao/BaseDAO.java new file mode 100644 index 00000000..91aa1df5 --- /dev/null +++ b/Spring/SourceCode/SpringBootSourceCode/src/test/java/org/example/dao/BaseDAO.java @@ -0,0 +1,63 @@ +package org.example.dao; + +import java.lang.reflect.Field; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +public class BaseDAO { + + public int executeUpdate(String sql, Object... args) throws SQLException { + Connection conn = JDBCUtils.getConnection(); + + PreparedStatement ps = conn.prepareStatement(sql); + + for (int i = 0; i < args.length; i++) { + ps.setString(i, args[i-1].toString()); + } + + int rows = ps.executeUpdate(); + ps.close(); + if (!conn.getAutoCommit()) { + conn.close(); + } + return rows; + } + + public List executeQuery(Class clazz,String sql, Object... args) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException { + Connection conn = JDBCUtils.getConnection(); + + PreparedStatement ps = conn.prepareStatement(sql); + + for (int i = 0; i < args.length; i++) { + ps.setString(i, args[i-1].toString()); + } + + ResultSet resultSet = ps.executeQuery(); + + //对返回值进行解析 + ResultSetMetaData resultMeta = resultSet.getMetaData(); + int count = resultMeta.getColumnCount(); + + List rows = new ArrayList(); + while(resultSet.next()){ + T t = clazz.newInstance(); + for (int i = 1; i < count; i++) { + Object object = resultSet.getObject(i); + String columnName = resultMeta.getColumnName(i); + Field field = clazz.getDeclaredField(columnName); + field.setAccessible(true); + field.set(t,object); + } + rows.add(t); + } + + + + ps.close(); + if (!conn.getAutoCommit()) { + conn.close(); + } + return rows; + } +} diff --git a/Spring/SourceCode/SpringBootSourceCode/src/test/java/org/example/dao/JDBCTest.java b/Spring/SourceCode/SpringBootSourceCode/src/test/java/org/example/dao/JDBCTest.java new file mode 100644 index 00000000..564e6c51 --- /dev/null +++ b/Spring/SourceCode/SpringBootSourceCode/src/test/java/org/example/dao/JDBCTest.java @@ -0,0 +1,45 @@ +package org.example.dao; + +import com.mysql.cj.jdbc.Driver; +import org.junit.Test; + +import java.sql.*; + +public class JDBCTest { + + + @Test + public void testJDBCConnnection() throws SQLException { + DriverManager.registerDriver(new Driver()); + + + + String url = "jdbc:mysql://127.0.0.1/test"; + String username= "root"; + String password = "long1011"; + Connection connection = DriverManager.getConnection(url, username, password); + + + String sql2 = "INSERT INTO user (name) values ('testname02')"; + connection.createStatement().executeUpdate(sql2); + + + String sql = "select * from user;"; + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(sql); + + while (resultSet.next()) { + String name = resultSet.getString("name"); + int id = resultSet.getInt("id"); + System.out.println("name: " + name+" id: " + id); + + } + + + resultSet.close(); + statement.close(); + connection.close(); + + + } +} diff --git a/Spring/SourceCode/SpringBootSourceCode/src/test/java/org/example/dao/JDBCUtils.java b/Spring/SourceCode/SpringBootSourceCode/src/test/java/org/example/dao/JDBCUtils.java new file mode 100644 index 00000000..dcef4453 --- /dev/null +++ b/Spring/SourceCode/SpringBootSourceCode/src/test/java/org/example/dao/JDBCUtils.java @@ -0,0 +1,56 @@ +package org.example.dao; + +import com.alibaba.druid.pool.DruidDataSourceFactory; + +import javax.sql.DataSource; +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +public class JDBCUtils { + static DataSource datasource = null; + + private static ThreadLocal tl = new ThreadLocal<>(); + + static { + Properties properties = new Properties(); + + InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"); + try{ + properties.load(in); + }catch(IOException e){ + throw new RuntimeException(e); + } + + try { + datasource = DruidDataSourceFactory.createDataSource(properties); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + public static Connection getConnection() throws SQLException { + Connection connection = tl.get(); + if(connection == null){ + connection = datasource.getConnection(); + tl.set(connection); + } + return connection; + } + + public static void freeConnection() throws SQLException { + + Connection connection = tl.get(); + if(connection != null){ + tl.remove(); + connection.setAutoCommit(false); + connection.close(); + } + + } + + +} diff --git a/Spring/Spring5/07 事件监听模型.md b/Spring/Spring5/07 事件监听模型.md deleted file mode 100644 index 5864cf6d..00000000 --- a/Spring/Spring5/07 事件监听模型.md +++ /dev/null @@ -1,39 +0,0 @@ -## 1 原理说明 -类似概念 -* 可观察对象、观察者。(观察者模式) -* 发布者、订阅者。(发布订阅机制) -* 事件、响应。(事件驱动、事件监听机制、响应式编程) -### 事件驱动模型和观察者模式 -一下名称具有相同的含义,都是观察着模式在不同场景下的实现。 -* Spring的事件驱动模型 -* 设计模式中的观察者模式 -* jdk中的observable和observer -* ui中事件监听机制 -* 消息队列的订阅发布机制 - -先是一种对象间的一对多的关系;最简单的如交通信号灯,信号灯是目标(一方),行人注视着信号灯(多方)。当目标发送改变(发布),观察者(订阅者)就可以接收到改变。 观察者如何处理(如行人如何走,是快走/慢走/不走,目标不会管的), 目标无需干涉;所以就松散耦合了它们之间的关系。 - - -### Spring中实现 -![](image/2022-10-18-12-06-37.png) - - - -### 类图关系 -* 事件 - -![](image/2022-10-18-12-07-26.png) - - -* 发布(动作) - * ApplicationContext 接口继承了 ApplicationEventPublisher,并在 AbstractApplicationContext 实现了具体代码,实际执行是委托给ApplicationEventMulticaster(可以认为是多播) - -![](image/2022-10-18-12-08-25.png) - - -* 监听 - -![](image/2022-10-18-12-08-47.png) - - - diff --git a/Spring/Springboot/01 内容简介.md b/Spring/Springboot/01 内容简介.md index 7e075501..96b2e2f6 100644 --- a/Spring/Springboot/01 内容简介.md +++ b/Spring/Springboot/01 内容简介.md @@ -9,6 +9,8 @@ > * 核心原理 > 。。。 +> 参考文档 +> https://blog.csdn.net/lin1214000999/article/details/105468338/ ## 1 springboot背景 springboot + springcloud ### 微服务 @@ -127,3 +129,7 @@ Spring Boot 是基于 Spring 框架基础上推出的一个全新的框架, 旨 * 非常多的starter * 引入了哪些场景这个场景的自动配置才会开启 * SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面 + + +## 知识体系 +![](image/2023-11-18-20-31-33.png) \ No newline at end of file diff --git a/Spring/Springboot/05 自动配置原理.md b/Spring/Springboot/05 自动配置原理.md index 4657bdc8..deb6892d 100644 --- a/Spring/Springboot/05 自动配置原理.md +++ b/Spring/Springboot/05 自动配置原理.md @@ -1,155 +1,5 @@ -## 1 springboot的启动过程 -springcontext.run到底干了什么 - -![](image/2023-01-09-10-47-27.png) - - -SpringApplication.run()到底干了什么 - -### 服务构建 - -调用SpringApplication的静态run方法。通过一系列配置创建SpringApplication类。 -1. 初始化资源加载器 -2. 初始化服务类型 -3. 初始化spring.factories中定义的初始化类。包括Initializer和Listener -4. 找到启动类 - -![](image/2023-01-09-10-56-25.png) - -```java - /** - * Create a new {@link SpringApplication} instance. The application context will load - * beans from the specified primary sources (see {@link SpringApplication class-level} - * documentation for details). The instance can be customized before calling - * {@link #run(String...)}. - * @param resourceLoader the resource loader to use - * @param primarySources the primary bean sources - * @see #run(Class, String[]) - * @see #setSources(Set) - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { - this.resourceLoader = resourceLoader; - Assert.notNull(primarySources, "PrimarySources must not be null"); - this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); - this.webApplicationType = WebApplicationType.deduceFromClasspath(); - this.bootstrapRegistryInitializers = new ArrayList<>( - getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); - setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); - setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); - this.mainApplicationClass = deduceMainApplicationClass(); - } -``` - - -### 环境准备 -调用SpringApplicationContext的run方法。 -1. 加载bootstrapContext上下文 -2. 获取并注册监听器。 -3. 加载环境变量,并发布环境变量加载完成的事件。(通过观察者模式) - - -![](image/2023-01-09-11-25-19.png) - -```java - /** - * Run the Spring application, creating and refreshing a new - * {@link ApplicationContext}. - * @param args the application arguments (usually passed from a Java main method) - * @return a running {@link ApplicationContext} - */ - public ConfigurableApplicationContext run(String... args) { - long startTime = System.nanoTime(); - DefaultBootstrapContext bootstrapContext = createBootstrapContext(); - ConfigurableApplicationContext context = null; - configureHeadlessProperty(); - SpringApplicationRunListeners listeners = getRunListeners(args); - listeners.starting(bootstrapContext, this.mainApplicationClass); - try { - ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); - ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); - configureIgnoreBeanInfo(environment); - Banner printedBanner = printBanner(environment); - context = createApplicationContext(); - context.setApplicationStartup(this.applicationStartup); - prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); - refreshContext(context); - afterRefresh(context, applicationArguments); - Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); - if (this.logStartupInfo) { - new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup); - } - listeners.started(context, timeTakenToStartup); - callRunners(context, applicationArguments); - } -``` - -### 容器创建 -在run方法中创建容器上下文SpringApplicationContext - - -![](image/2023-01-09-11-39-47.png) - -1. 默认创建AnnotationConfigServletWebServerApplicationContext。在该类中调用两个注解处理方法。 - -```java - public AnnotationConfigServletWebServerApplicationContext() { - this.reader = new AnnotatedBeanDefinitionReader(this); - this.scanner = new ClassPathBeanDefinitionScanner(this); - } -``` -2. 构建conttext。加载Initializer,注册启动参数,加载postProcess. - -```java - private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, - ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, - ApplicationArguments applicationArguments, Banner printedBanner) { - context.setEnvironment(environment); - postProcessApplicationContext(context); - applyInitializers(context); - listeners.contextPrepared(context); - bootstrapContext.close(context); - if (this.logStartupInfo) { - logStartupInfo(context.getParent() == null); - logStartupProfileInfo(context); - } - // Add boot specific singleton beans - ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); - beanFactory.registerSingleton("springApplicationArguments", applicationArguments); - if (printedBanner != null) { - beanFactory.registerSingleton("springBootBanner", printedBanner); - } - if (beanFactory instanceof AbstractAutowireCapableBeanFactory) { - ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences); - if (beanFactory instanceof DefaultListableBeanFactory) { - ((DefaultListableBeanFactory) beanFactory) - .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); - } - } - if (this.lazyInitialization) { - context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); - } - context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context)); - // Load the sources - Set sources = getAllSources(); - Assert.notEmpty(sources, "Sources must not be empty"); - load(context, sources.toArray(new Object[0])); - } -``` -3. 发布资源监听事件 -```java - listeners.contextLoaded(context); - -``` - -### 填充容器——自动装配 - -![](image/2023-01-09-11-40-17.png) -1. refreshContext(conext) -2. 发布启动完成事件,调用自定义实现的runner接口。 - -## 2 自动配置加载的过程 +## 1 自动配置加载的过程 ### 加载过程 diff --git a/Spring/Springboot/06 Web开发5-原生组件.md b/Spring/Springboot/06 Web开发5-原生组件.md index e69de29b..a399437c 100644 --- a/Spring/Springboot/06 Web开发5-原生组件.md +++ b/Spring/Springboot/06 Web开发5-原生组件.md @@ -0,0 +1,200 @@ +## 0 概述 + +使用的标准方法 + +```java +@ServletComponentScan(basePackages = "com.atguigu.admin") :指定原生Servlet组件都放在那里 +@WebServlet(urlPatterns = "/my"):效果:直接响应,没有经过Spring的拦截器? +@WebFilter(urlPatterns={"/css/*","/images/*"}) +@WebListener +``` + +## WebServlet + +```java +@WebServlet(urlPatterns = "/path2/*") +public class MyServlet extends HttpServlet { + + @Override + protected void doGet (HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + PrintWriter writer = resp.getWriter(); + writer.println("response from servlet "); + } +} +``` + +## WebFilter + +```java +@WebFilter(urlPatterns = "/*") +public class MyFilter implements Filter{ + + @Override + public void init (FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter (ServletRequest request, + ServletResponse response, FilterChain chain) + throws IOException, ServletException { + String url = request instanceof HttpServletRequest ? + ((HttpServletRequest) request).getRequestURL().toString() : "N/A"; + System.out.println("from filter, processing url: "+url); + chain.doFilter(request, response); + } + + @Override + public void destroy () { + + } +} +``` + + +## WebListener + +```java +@WebListener +public class MyServletListener implements ServletContextListener{ + + @Override + public void contextInitialized (ServletContextEvent sce) { + System.out.println("from ServletContextListener: " + + " context initialized"); + + } + + @Override + public void contextDestroyed (ServletContextEvent sce) { + + } +} +``` + + +### DispatcherServlet + +DispatchServlet 如何注册进来 +● 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。 +● 通过 ServletRegistrationBean 把 DispatcherServlet 配置进来。 +● 默认映射的是 / 路径。 + +![](image/2023-11-18-16-28-24.png) + + +### 使用RegistrationBean +ServletRegistrationBean, FilterRegistrationBean, and ServletListenerRegistrationBean + +```java +@Configuration +public class MyRegistConfig { + + @Bean + public ServletRegistrationBean myServlet(){ + MyServlet myServlet = new MyServlet(); + + return new ServletRegistrationBean(myServlet,"/my","/my02"); + } + + + @Bean + public FilterRegistrationBean myFilter(){ + + MyFilter myFilter = new MyFilter(); +// return new FilterRegistrationBean(myFilter,myServlet()); + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter); + filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*")); + return filterRegistrationBean; + } + + @Bean + public ServletListenerRegistrationBean myListener(){ + MySwervletContextListener mySwervletContextListener = new MySwervletContextListener(); + return new ServletListenerRegistrationBean(mySwervletContextListener); + } +} +``` + +## 2 嵌入式Servlet容器 + +### 方法 +● 默认支持的webServer + ○ Tomcat, Jetty, or Undertow + ○ ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器 +● 切换服务器 + +```java + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + +``` + +### 原理 + +* SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat +* web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext +* ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂---> Servlet 的web服务器) +* SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory +* 底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration +* ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类) +* ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory +* TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start(); +* 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在) + + +## 3 定制Servlet容器 +● 实现 WebServerFactoryCustomizer + ○ 把配置文件的值和ServletWebServerFactory 进行绑定 +● 修改配置文件 server.xxx +● 直接自定义 ConfigurableServletWebServerFactory + +xxxxxCustomizer:定制化器,可以改变xxxx的默认规则 + +```java +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.stereotype.Component; + +@Component +public class CustomizationBean implements WebServerFactoryCustomizer { + + @Override + public void customize(ConfigurableServletWebServerFactory server) { + server.setPort(9000); + } + +} +``` + +### 定制化的常见方式 +多级定制化方式 +● 修改配置文件; +● xxxxxCustomizer; +● 编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器 +● Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件 + +```java +@Configuration +public class AdminWebConfig implements WebMvcConfigurer +``` + +● @EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能 + ○ 原理 + ○ 1、WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页..... + ○ 2、一旦使用 @EnableWebMvc 、。会 @Import(DelegatingWebMvcConfiguration.class) + ○ 3、DelegatingWebMvcConfiguration 的 作用,只保证SpringMVC最基本的使用 + ■ 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效 + ■ 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取 + ■ public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport + ○ 4、WebMvcAutoConfiguration 里面的配置要能生效 必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) + ○ 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。 \ No newline at end of file diff --git a/Spring/Springboot/07 JDBC.md b/Spring/Springboot/07 数据访问-1jdbc.md similarity index 94% rename from Spring/Springboot/07 JDBC.md rename to Spring/Springboot/07 数据访问-1jdbc.md index a279117d..4f3b6e1c 100644 --- a/Spring/Springboot/07 JDBC.md +++ b/Spring/Springboot/07 数据访问-1jdbc.md @@ -1,7 +1,10 @@ ## 1 jdbc配置 > 数据库与数据源不是同一个东西。。。 - +> 三层关键概念需要理解 +> 1. 数据库驱动mysql、hsqldb +> 2. 数据源datasource和数据库连接池Harica、Druid +> 3. 数据库操作工具JDBCTemplates、Mybatis ### 数据源配置 pom.xml @@ -13,7 +16,7 @@ pom.xml ``` -### 嵌入式数据库支持 +### 嵌入式数据库驱动 嵌入式数据库支持:H2、HSQL、Derby。不需要任何配置,被集成到springboot的jar包当中。 ``` @@ -23,7 +26,7 @@ pom.xml ``` -### 连接mysql数据库 +### mysql数据库驱动 * 引入mysql依赖包 @@ -50,7 +53,7 @@ JNDI不需要用户使用java代码与数据库建立连接,而是将连接交 spring.datasource.jndi-name=java:jboss/datasources/customers ``` -## 2 使用jdbcTemplate操作数据库 +## 2 JdbcTemplate操作数据库 ### 准备数据库 ``` @@ -119,7 +122,7 @@ public interface UserService { * 通过jdbcTemplate实现Userservice中定义的操作。 -``` +```java @Service public class UserServiceImpl implements UserService { @@ -165,7 +168,7 @@ public class UserServiceImpl implements UserService { ### 编写单元测试用例 创建对UserService的单元测试用例,通过创建、删除和查询来验证数据库操作的正确性。 -``` +```java @RunWith(SpringRunner.class) @SpringBootTest public class Chapter31ApplicationTests { diff --git a/Spring/Springboot/07 数据访问-2数据源.md b/Spring/Springboot/07 数据访问-2数据源.md new file mode 100644 index 00000000..a6f4613d --- /dev/null +++ b/Spring/Springboot/07 数据访问-2数据源.md @@ -0,0 +1,207 @@ +## 1 基本概念 + +### JDBC + +java数据库链接,java database connectivity。java语言用来规范客户访问数据库的应用程序接口。提供了查询、更新数据库的方法。java.sql与javax.sql主要包括以下类: + +* DriverManager:负责加载不同的驱动程序Driver,返回相应的数据库连接Connection。 +* Driver:对应数据库的驱动程序。 +* Connection:数据库连接,负责与数据库进行通信。可以产生SQL的statement. +* Statement:用来执行SQL查询和更新。 +* CallableStatement:用以调用数据库中的存储过程。 +* SQLException:代表数据库联机额的建立和关闭和SQL语句中发生的例情况。 + +### 数据源 + +1. 封装关于数据库访问的各种参数,实现统一管理。 +2. 通过数据库的连接池管理,节省开销并提高效率。 + +> 简单理解,就是在用户程序与数据库之间,建立新的缓冲地带,用来对用户的请求进行优化,对数据库的访问进行整合。 + +常见的数据源:DBCP、C3P0、Druid、HikariCP。 + + +## 2 HikariCP默认数据源配置 + +### 通用配置 + +以spring.datasource.*的形式存在,包括数据库连接地址、用户名、密码。 +``` +spring.datasource.url=jdbc:mysql://localhost:3306/test +spring.datasource.username=root +spring.datasource.password=123456 +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +``` + +### 数据源连接配置 + +以spring.datasource.<数据源名称>.*的形式存在, +``` +spring.datasource.hikari.minimum-idle=10//最小空闲连接 +spring.datasource.hikari.maximum-pool-size=20//最大连接数 +spring.datasource.hikari.idle-timeout=500000//控线连接超时时间 +spring.datasource.hikari.max-lifetime=540000//最大存活时间 +spring.datasource.hikari.connection-timeout=60000//连接超时时间 +spring.datasource.hikari.connection-test-query=SELECT 1//用于测试连接是否可用的查询语句 +``` + +## 3 druid数据源 + +### pom.xml配置druid依赖 + +``` + + com.alibaba + druid-spring-boot-starter + 1.1.21 + +``` + +### application.properties配置数据库连接信息 + +以spring.datasource.druid作为前缀 +``` +spring.datasource.druid.url=jdbc:mysql://localhost:3306/test +spring.datasource.druid.username=root +spring.datasource.druid.password= +spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver +``` + +### 配置druid连接池 +> 具体的信息可以自己查询相关的内容。 +``` +spring.datasource.druid.initialSize=10 +spring.datasource.druid.maxActive=20 +spring.datasource.druid.maxWait=60000 +spring.datasource.druid.minIdle=1 +spring.datasource.druid.timeBetweenEvictionRunsMillis=60000 +spring.datasource.druid.minEvictableIdleTimeMillis=300000 +spring.datasource.druid.testWhileIdle=true +spring.datasource.druid.testOnBorrow=true +spring.datasource.druid.testOnReturn=false +spring.datasource.druid.poolPreparedStatements=true +spring.datasource.druid.maxOpenPreparedStatements=20 +spring.datasource.druid.validationQuery=SELECT 1 +spring.datasource.druid.validation-query-timeout=500 +spring.datasource.druid.filters=stat +``` + +yaml实例 + +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/db_account + username: root + password: 123456 + driver-class-name: com.mysql.jdbc.Driver + + druid: + aop-patterns: com.atguigu.admin.* #监控SpringBean + filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙) + + stat-view-servlet: # 配置监控页功能 + enabled: true + login-username: admin + login-password: admin + resetEnable: false + + web-stat-filter: # 监控web + enabled: true + urlPattern: /* + exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' + + + filter: + stat: # 对上面filters里面的stat的详细配置 + slow-sql-millis: 1000 + logSlowSql: true + enabled: true + wall: + enabled: true + config: + drop-table-allow: false +``` + + + +### 配置druid监控 + +* 在pom.xml中增加依赖 + +``` + + org.springframework.boot + spring-boot-starter-actuator + +``` + +* 在application.properties中添加druid监控配置 + +``` +spring.datasource.druid.stat-view-servlet.enabled=true +spring.datasource.druid.stat-view-servlet.url-pattern=/druid/* +spring.datasource.druid.stat-view-servlet.reset-enable=true +spring.datasource.druid.stat-view-servlet.login-username=admin +spring.datasource.druid.stat-view-servlet.login-password=admin +``` + +* 在xml中添加监控配置(可选,通过配置文件配置即可) + +```xml +需要给数据源中配置如下属性;可以允许多个filter,多个用,分割;如: + + + + + + + + +使用 slowSqlMillis 定义慢SQL的时长 +``` + +## 4 原理 + +### 自动配置的类 +● DataSourceAutoConfiguration : 数据源的自动配置 + ○ 修改数据源相关的配置:spring.datasource + ○ 数据库连接池的配置,是自己容器中没有DataSource才自动配置的 + ○ 底层配置好的连接池是:HikariDataSource + +```java + @Configuration(proxyBeanMethods = false) + @Conditional(PooledDataSourceCondition.class) + @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) + @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, + DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, + DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) + protected static class PooledDataSourceConfiguration +``` + + +● DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置 +● JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud + ○ 可以修改这个配置项@ConfigurationProperties(prefix = "spring.jdbc") 来修改JdbcTemplate + ○ @Bean@Primary JdbcTemplate;容器中有这个组件 +● JndiDataSourceAutoConfiguration: jndi的自动配置 +● XADataSourceAutoConfiguration: 分布式事务相关的 + +### Druid自动配置原理 +分析自动配置 +● 扩展配置项 spring.datasource.druid +● DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns +● DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启 +● DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启 +● DruidFilterConfiguration.class}) 所有Druid自己filter的配置 + +```java + private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat"; + private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config"; + private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding"; + private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j"; + private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j"; + private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2"; + private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log"; + private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall"; +``` \ No newline at end of file diff --git a/Spring/Springboot/09 MyBatis.md b/Spring/Springboot/07 数据访问-3MyBatis.md similarity index 61% rename from Spring/Springboot/09 MyBatis.md rename to Spring/Springboot/07 数据访问-3MyBatis.md index defd438a..741cf460 100644 --- a/Spring/Springboot/09 MyBatis.md +++ b/Spring/Springboot/07 数据访问-3MyBatis.md @@ -1,8 +1,22 @@ > 对象关系映射模型Hibernate。用来实现非常轻量级的对象的封装。将对象与数据库建立映射关系。实现增删查改。 > MyBatis与Hibernate非常相似。对象关系映射模型ORG。java对象与关系数据库映射的模型。 + + + ## 1 配置MyBatis +### 最佳实践 + +最佳实战: +● 引入mybatis-starter +● 配置application.yaml中,指定mapper-location位置即可 +● 编写Mapper接口并标注@Mapper注解 +● 简单方法直接注解方式 +● 复杂方法编写mapper.xml进行绑定映射 +● @MapperScan("com.atguigu.admin.mapper") 简化,其他的接口就可以不用标注@Mapper注解 + + ### 在pom.xml中添加MyBatis依赖 ``` @@ -21,7 +35,7 @@ ### 配置数据库连接 在application.properties中配置mysql的链接配置 -``` +```sh spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password= @@ -30,7 +44,7 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ### 创建关系数据表 -``` +```sql CREATE TABLE `User` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL, @@ -41,7 +55,7 @@ CREATE TABLE `User` ( ### 创建数据表的java对象 -``` +```java @Data @NoArgsConstructor public class User { @@ -58,20 +72,6 @@ public class User { } ``` -### 创建数据表的操作接口 - -``` -@Mapper -public interface UserMapper { - - @Select("SELECT * FROM USER WHERE NAME = #{name}") - User findByName(@Param("name") String name); - - @Insert("INSERT INTO USER(NAME, AGE) VALUES(#{name}, #{age})") - int insert(@Param("name") String name, @Param("age") Integer age); - -} -``` ## 2 MyBatis参数传递 @@ -99,9 +99,25 @@ userMapper.insertByMap(map); @Insert("INSERT INTO USER(NAME, AGE) VALUES(#{name}, #{age})") int insertByUser(User user); ``` -## 3 增删查改操作 +## 3 注解模式 + +### 创建数据表的操作接口 + +```java +@Mapper +public interface UserMapper { + + @Select("SELECT * FROM USER WHERE NAME = #{name}") + User findByName(@Param("name") String name); + + @Insert("INSERT INTO USER(NAME, AGE) VALUES(#{name}, #{age})") + int insert(@Param("name") String name, @Param("age") Integer age); + +} ``` +### 增删改查操作 +```java public interface UserMapper { @Select("SELECT * FROM user WHERE name = #{name}") @@ -118,7 +134,7 @@ public interface UserMapper { } ``` 对增删查改的调用 -``` +```java @Transactional @RunWith(SpringRunner.class) @SpringBootTest @@ -147,7 +163,7 @@ public class ApplicationTests { } ``` -## 4 使用MyBatis的XML方式 +## 4 XML方式 ### 在应用主类中增加mapper的扫描包配置: ``` @@ -215,5 +231,93 @@ public class Chapter36ApplicationTests { Assert.assertEquals(20, u.getAge().intValue()); } +} +``` + +## 5 整合 MyBatis-Plus 完成CRUD +### 什么是MyBatis-Plus +MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 +mybatis plus 官网 +建议安装 MybatisX 插件 + +### 引入 + +```xml + + com.baomidou + mybatis-plus-boot-starter + 3.4.1 + +``` +### 自动配置 +● MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对mybatis-plus的定制 +● SqlSessionFactory 自动配置好。底层是容器中默认的数据源 +● mapperLocations 自动配置好的。有默认值。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下 +● 容器中也自动配置好了 SqlSessionTemplate +● @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan("com.atguigu.admin.mapper") 批量扫描就行 + +### 优点 + +优点: +● 只需要我们的Mapper继承 BaseMapper 就可以拥有crud能力 + + +### CRUD +DAO层 +```java + @GetMapping("/user/delete/{id}") + public String deleteUser(@PathVariable("id") Long id, + @RequestParam(value = "pn",defaultValue = "1")Integer pn, + RedirectAttributes ra){ + + userService.removeById(id); + + ra.addAttribute("pn",pn); + return "redirect:/dynamic_table"; + } + + + @GetMapping("/dynamic_table") + public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){ + //表格内容的遍历 +// response.sendError +// List users = Arrays.asList(new User("zhangsan", "123456"), +// new User("lisi", "123444"), +// new User("haha", "aaaaa"), +// new User("hehe ", "aaddd")); +// model.addAttribute("users",users); +// +// if(users.size()>3){ +// throw new UserTooManyException(); +// } + //从数据库中查出user表中的用户进行展示 + + //构造分页参数 + Page page = new Page<>(pn, 2); + //调用page进行分页 + Page userPage = userService.page(page, null); + + +// userPage.getRecords() +// userPage.getCurrent() +// userPage.getPages() + + + model.addAttribute("users",userPage); + + return "table/dynamic_table"; + } +``` + +### Service层 +```java +@Service +public class UserServiceImpl extends ServiceImpl implements UserService { + + +} + +public interface UserService extends IService { + } ``` \ No newline at end of file diff --git a/Spring/Springboot/07 数据访问-4Redis.md b/Spring/Springboot/07 数据访问-4Redis.md new file mode 100644 index 00000000..a95d2d95 --- /dev/null +++ b/Spring/Springboot/07 数据访问-4Redis.md @@ -0,0 +1,68 @@ +## 1 简介 + +### 引入 +NoSQL +Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。 + +### 自动配置 + +```xml + + org.springframework.boot + spring-boot-starter-data-redis + +``` + +![](image/2023-11-18-16-53-56.png) + + +### 配置原理 + +自动配置: +● RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置 +● 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration +● 自动注入了RedisTemplate : xxxTemplate; +● 自动注入了StringRedisTemplate;k:v都是String +● key:value +● 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis + +### 操作 + +```java + @Test + void testRedis(){ + ValueOperations operations = redisTemplate.opsForValue(); + + operations.set("hello","world"); + + String hello = operations.get("hello"); + System.out.println(hello); + } +``` + +### 切换jedis + +```java + + org.springframework.boot + spring-boot-starter-data-redis + + + + + redis.clients + jedis + +``` + +```yml +spring: + redis: + host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com + port: 6379 + password: lfy:Lfy123456 + client-type: jedis + jedis: + pool: + max-active: 10 +``` \ No newline at end of file diff --git a/Spring/Springboot/08 单元测试.md b/Spring/Springboot/08 单元测试.md new file mode 100644 index 00000000..70ad74b8 --- /dev/null +++ b/Spring/Springboot/08 单元测试.md @@ -0,0 +1,491 @@ + +> springboot with junit4 &junit5 https://segmentfault.com/a/1190000040803747 + +## 1 概述 + +### spring-boot-starter-test +SpringBoot中有关测试的框架,主要来源于 spring-boot-starter-test。一旦依赖了spring-boot-starter-test,下面这些类库将被一同依赖进去: +* JUnit:java测试事实上的标准。 +* Spring Test & Spring Boot Test:Spring的测试支持。 +* AssertJ:提供了流式的断言方式。 +* Hamcrest:提供了丰富的matcher。 +* Mockito:mock框架,可以按类型创建mock对象,可以根据方法参数指定特定的响应,也支持对于mock调用过程的断言。 +* JSONassert:为JSON提供了断言功能。 +* JsonPath:为JSON提供了XPATH功能。 + +### 多种测试模式 + + +* @RunWith(SpringJUnit4ClassRunner.class)启动Spring上下文环境。 +* @RunWith(MockitoJUnitRunner.class)mockito方法进行测试。对底层的类进行mock,测试速度快。 +* @RunWith(PowerMockRunner.class)powermock方法进行测试。对底层类进行mock,测试方法更全面。 + + +### junit5 变化 + +Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库 +作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。 +JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage +* JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。 +* JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。 +* JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。 + +![](image/2023-11-18-18-53-47.png) + +```java +@SpringBootTest +class Boot05WebAdminApplicationTests { + + + @Test + void contextLoads() { + + } +} + +``` + +SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)。JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage + +```xml + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + +``` + +以前:`@SpringBootTest + @RunWith(SpringTest.class)`。SpringBoot整合Junit以后。编写测试方法:@Test标注(注意需要使用junit5版本的注解)。Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚 + +### junit4 & junit5对比 + + +| 功能 | JUnit4 | JUnit5 | +|------------------|--------------|--------------| +| 声明一种测试方法 | @Test | @Test | +| 在当前类中的所有测试方法之前执行 | @BeforeClass | @BeforeAll | +| 在当前类中的所有测试方法之后执行 | @AfterClass | @AfterAll | +| 在每个测试方法之前执行 | @Before | @BeforeEach | +| 在每个测试方法之后执行 | @After | @AfterEach | +| 禁用测试方法/类 | @Ignore | @Disabled | +| 测试工厂进行动态测试 | NA | @TestFactory | +| 嵌套测试 | NA | @Nested | +| 标记和过滤 | @Category | @Tag | +| 注册自定义扩展 | NA | @ExtendWith | + + +### RunWith 和 ExtendWith + +在 JUnit4 版本,在测试类加 @SpringBootTest 注解时,同样要加上 @RunWith(SpringRunner.class)才生效,即: +``` +@SpringBootTest +@RunWith(SpringRunner.class) +class HrServiceTest { +... +} +``` +但在 JUnit5 中,官网告知 @RunWith 的功能都被 @ExtendWith 替代,即原 @RunWith(SpringRunner.class) 被同功能的 @ExtendWith(SpringExtension.class) 替代。但 JUnit5 中 @SpringBootTest 注解中已经默认包含了 @ExtendWith(SpringExtension.class)。 + +因此,在 JUnit5 中只需要单独使用 @SpringBootTest 注解即可。其他需要自定义拓展的再用 @ExtendWith,不要再用 @RunWith 了。 + + +### mockito + +Mockito 框架中最核心的两个概念就是 Mock 和 Stub。测试时不是真正的操作外部资源,而是通过自定义的代码进行模拟操作。我们可以对任何的依赖进行模拟,从而使测试的行为不需要任何准备工作或者不具备任何副作用。 + +1. 当我们在测试时,如果只关心某个操作是否执行过,而不关心这个操作的具体行为,这种技术称为 mock。比如我们测试的代码会执行发送邮件的操作,我们对这个操作进行 mock;测试的时候我们只关心是否调用了发送邮件的操作,而不关心邮件是否确实发送出去了。 + +2. 另一种情况,当我们关心操作的具体行为,或者操作的返回结果的时候,我们通过执行预设的操作来代替目标操作,或者返回预设的结果作为目标操作的返回结果。这种对操作的模拟行为称为 stub(打桩)。比如我们测试代码的异常处理机制是否正常,我们可以对某处代码进行 stub,让它抛出异常。再比如我们测试的代码需要向数据库插入一条数据,我们可以对插入数据的代码进行stub,让它始终返回1,表示数据插入成功。 + +### powermock + +需要手动引入测试类。依赖mockito,注意版本的对应关系。 + +```xml + + org.powermock + powermock-api-mockito2 + + + + org.powermock + powermock-module-junit4 + +``` + +### 断言测试 +![](image/2023-11-18-18-58-17.png) + +```java +@Test +@DisplayName("simple assertion") +public void simple() { + assertEquals(3, 1 + 2, "simple math"); + assertNotEquals(3, 1 + 1); + + assertNotSame(new Object(), new Object()); + Object obj = new Object(); + assertSame(obj, obj); + + assertFalse(1 > 2); + assertTrue(1 < 2); + + assertNull(null); + assertNotNull(new Object()); +} +``` +数组断言 +```java + +@Test +@DisplayName("array assertion") +public void array() { + assertArrayEquals(new int[]{1, 2}, new int[] {1, 2}); +} +``` +组合断言。assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言 + +```java +@Test +@DisplayName("assert all") +public void all() { + assertAll("Math", + () -> assertEquals(2, 1 + 1), + () -> assertTrue(1 > 0) + ); +} +``` +异常断言 +在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。 + +```java +@Test +@DisplayName("异常测试") +public void exceptionTest() { + ArithmeticException exception = Assertions.assertThrows( + //扔出断言异常 + ArithmeticException.class, () -> System.out.println(1 % 0)); + +} +``` + +超时断言 +Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间 + +```java +@Test +@DisplayName("超时测试") +public void timeoutTest() { + //如果测试方法时间超过1s将会异常 + Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500)); +} +``` +快速失败 +通过 fail 方法直接使得测试失败 +```java +@Test +@DisplayName("fail") +public void shouldFail() { + fail("This should fail"); +} +``` + +### 前置条件(assumptions) + +JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。 +```java +@DisplayName("前置条件") +public class AssumptionsTest { + private final String environment = "DEV"; + + @Test + @DisplayName("simple") + public void simpleAssume() { + assumeTrue(Objects.equals(this.environment, "DEV")); + assumeFalse(() -> Objects.equals(this.environment, "PROD")); + } + + @Test + @DisplayName("assume then do") + public void assumeThenDo() { + assumingThat( + Objects.equals(this.environment, "DEV"), + () -> System.out.println("In DEV") + ); + } +} +``` + +assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。 + + +## 2 使用 + +### 注解 + +@RunWith: +1. 表示运行方式,@RunWith(JUnit4TestRunner)、@RunWith(SpringRunner.class)、@RunWith(PowerMockRunner.class) 三种运行方式,分别在不同的场景中使用。 +2. 当一个类用@RunWith注释或继承一个用@RunWith注释的类时,JUnit将调用它所引用的类来运行该类中的测试而不是开发者去在junit内部去构建它。我们在开发过程中使用这个特性 + +@SpringBootTest: +1. 注解制定了一个测试类运行了Spring Boot环境。提供了以下一些特性: + 1. 当没有特定的ContextConfiguration#loader()(@ContextConfiguration(loader=...))被定义那么就是SpringBootContextLoader作为默认的ContextLoader。 + 2. 自动搜索到SpringBootConfiguration注解的文件。 + 3. 允许自动注入Environment类读取配置文件。 + 4. 提供一个webEnvironment环境,可以完整的允许一个web环境使用随机的端口或者自定义的端口。 + 5. 注册了TestRestTemplate类可以去做接口调用。 + +2. 添加这个就能取到spring中的容器的实例,如果配置了@Autowired那么就自动将对象注入。 + +### 基本测试用例 + +Springboot整合JUnit的步骤 +1. 导入测试对应的starter + +2. 测试类使用@SpringBootTest修饰 + +3.使用自动装配的形式添加要测试的对象。 + +```java +@SpringBootTest(classes = {SpringbootJunitApplication.class}) +class SpringbootJunitApplicationTests { + + @Autowired + private UsersDao users; + + @Test + void contextLoads() { + users.save(); + } + +} +``` + + +### mock单元测试 +因为单元测试不用启动 Spring 容器,则无需加 @SpringBootTest,因为要用到 Mockito,只需要自定义拓展 MockitoExtension.class 即可,依赖简单,运行速度更快。 + +可以明显看到,单元测试写的代码,怎么是被测试代码长度的好几倍?其实单元测试的代码长度比较固定,都是造数据和打桩,但如果针对越复杂逻辑的代码写单元测试,还是越划算的 +```java +@ExtendWith(MockitoExtension.class) +class HrServiceTest { + @Mock + private OrmDepartmentDao ormDepartmentDao; + @Mock + private OrmUserDao ormUserDao; + @InjectMocks + private HrService hrService; + + @DisplayName("根据部门名称,查询用户") + @Test + void findUserByDeptName() { + Long deptId = 100L; + String deptName = "行政部"; + OrmDepartmentPO ormDepartmentPO = new OrmDepartmentPO(); + ormDepartmentPO.setId(deptId); + ormDepartmentPO.setDepartmentName(deptName); + OrmUserPO user1 = new OrmUserPO(); + user1.setId(1L); + user1.setUsername("001"); + user1.setDepartmentId(deptId); + OrmUserPO user2 = new OrmUserPO(); + user2.setId(2L); + user2.setUsername("002"); + user2.setDepartmentId(deptId); + List userList = new ArrayList<>(); + userList.add(user1); + userList.add(user2); + + Mockito.when(ormDepartmentDao.findOneByDepartmentName(deptName)) + .thenReturn( + Optional.ofNullable(ormDepartmentPO) + .filter(dept -> deptName.equals(dept.getDepartmentName())) + ); + Mockito.doReturn( + userList.stream() + .filter(user -> deptId.equals(user.getDepartmentId())) + .collect(Collectors.toList()) + ).when(ormUserDao).findByDepartmentId(deptId); + + List result1 = hrService.findUserByDeptName(deptName); + List result2 = hrService.findUserByDeptName(deptName + "error"); + + Assertions.assertEquals(userList, result1); + Assertions.assertEquals(Collections.emptyList(), result2); + } +``` + + +### 集成单元测试 +还是那个方法,如果使用Spring上下文,真实的调用方法依赖,可直接用下列方式 +```java +@SpringBootTest +class HrServiceTest { + @Autowired + private HrService hrService; + + @DisplayName("根据部门名称,查询用户") + @Test + void findUserByDeptName() { + List userList = hrService.findUserByDeptName("行政部"); + Assertions.assertTrue(userList.size() > 0); + } +} +``` + + +还可以使用@MockBean、@SpyBean替换Spring上下文中的对应的Bean: +```java +@SpringBootTest +class HrServiceTest { + @Autowired + private HrService hrService; + @SpyBean + private OrmDepartmentDao ormDepartmentDao; + + @DisplayName("根据部门名称,查询用户") + @Test + void findUserByDeptName() { + String deptName="行政部"; + OrmDepartmentPO ormDepartmentPO = new OrmDepartmentPO(); + ormDepartmentPO.setDepartmentName(deptName); + Mockito.when(ormDepartmentDao.findOneByDepartmentName(ArgumentMatchers.anyString())) + .thenReturn(Optional.of(ormDepartmentPO)); + List userList = hrService.findUserByDeptName(deptName); + Assertions.assertTrue(userList.size() > 0); + } +} +``` + +### 嵌套测试 + +JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。 + +```java +@DisplayName("A stack") +class TestingAStackDemo { + + Stack stack; + + @Test + @DisplayName("is instantiated with new Stack()") + void isInstantiatedWithNew() { + new Stack<>(); + } + + @Nested + @DisplayName("when new") + class WhenNew { + + @BeforeEach + void createNewStack() { + stack = new Stack<>(); + } + + @Test + @DisplayName("is empty") + void isEmpty() { + assertTrue(stack.isEmpty()); + } + + @Test + @DisplayName("throws EmptyStackException when popped") + void throwsExceptionWhenPopped() { + assertThrows(EmptyStackException.class, stack::pop); + } + + @Test + @DisplayName("throws EmptyStackException when peeked") + void throwsExceptionWhenPeeked() { + assertThrows(EmptyStackException.class, stack::peek); + } + + @Nested + @DisplayName("after pushing an element") + class AfterPushing { + + String anElement = "an element"; + + @BeforeEach + void pushAnElement() { + stack.push(anElement); + } + + @Test + @DisplayName("it is no longer empty") + void isNotEmpty() { + assertFalse(stack.isEmpty()); + } + + @Test + @DisplayName("returns the element when popped and is empty") + void returnElementWhenPopped() { + assertEquals(anElement, stack.pop()); + assertTrue(stack.isEmpty()); + } + + @Test + @DisplayName("returns the element when peeked but remains not empty") + void returnElementWhenPeeked() { + assertEquals(anElement, stack.peek()); + assertFalse(stack.isEmpty()); + } + } + } +} +``` + +### 参数化测试 + + +参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。 + +利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。 + +@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型 +@NullSource: 表示为参数化测试提供一个null的入参 +@EnumSource: 表示为参数化测试提供一个枚举入参 +@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参 +@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流) + +当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。 + + +```java +@ParameterizedTest +@ValueSource(strings = {"one", "two", "three"}) +@DisplayName("参数化测试1") +public void parameterizedTest1(String string) { + System.out.println(string); + Assertions.assertTrue(StringUtils.isNotBlank(string)); +} + + +@ParameterizedTest +@MethodSource("method") //指定方法名 +@DisplayName("方法来源参数") +public void testWithExplicitLocalMethodSource(String name) { + System.out.println(name); + Assertions.assertNotNull(name); +} + +static Stream method() { + return Stream.of("apple", "banana"); +} +``` + +### 迁移指南 + +在进行迁移的时候需要注意如下的变化: +● 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。 +● 把@Before 和@After 替换成@BeforeEach 和@AfterEach。 +● 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。 +● 把@Ignore 替换成@Disabled。 +● 把@Category 替换成@Tag。 +● 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。 \ No newline at end of file diff --git a/Spring/Springboot/08 数据源.md b/Spring/Springboot/08 数据源.md deleted file mode 100644 index 0b11fb8f..00000000 --- a/Spring/Springboot/08 数据源.md +++ /dev/null @@ -1,109 +0,0 @@ -## 1 基本概念 - -### JDBC - -java数据库链接,java database connectivity。java语言用来规范客户访问数据库的应用程序接口。提供了查询、更新数据库的方法。java.sql与javax.sql主要包括以下类: - -* DriverManager:负责加载不同的驱动程序Driver,返回相应的数据库连接Connection。 -* Driver:对应数据库的驱动程序。 -* Connection:数据库连接,负责与数据库进行通信。可以产生SQL的statement. -* Statement:用来执行SQL查询和更新。 -* CallableStatement:用以调用数据库中的存储过程。 -* SQLException:代表数据库联机额的建立和关闭和SQL语句中发生的例情况。 - -### 数据源 - -1. 封装关于数据库访问的各种参数,实现统一管理。 -2. 通过数据库的连接池管理,节省开销并提高效率。 - -> 简单理解,就是在用户程序与数据库之间,建立新的缓冲地带,用来对用户的请求进行优化,对数据库的访问进行整合。 - -常见的数据源:DBCP、C3P0、Druid、HikariCP。 - - -## 2 HikariCP默认数据源配置 - -### 通用配置 - -以spring.datasource.*的形式存在,包括数据库连接地址、用户名、密码。 -``` -spring.datasource.url=jdbc:mysql://localhost:3306/test -spring.datasource.username=root -spring.datasource.password=123456 -spring.datasource.driver-class-name=com.mysql.jdbc.Driver -``` - -### 数据源连接配置 - -以spring.datasource.<数据源名称>.*的形式存在, -``` -spring.datasource.hikari.minimum-idle=10//最小空闲连接 -spring.datasource.hikari.maximum-pool-size=20//最大连接数 -spring.datasource.hikari.idle-timeout=500000//控线连接超时时间 -spring.datasource.hikari.max-lifetime=540000//最大存活时间 -spring.datasource.hikari.connection-timeout=60000//连接超时时间 -spring.datasource.hikari.connection-test-query=SELECT 1//用于测试连接是否可用的查询语句 -``` - -## 3 druid数据源 - -### pom.xml配置druid依赖 - -``` - - com.alibaba - druid-spring-boot-starter - 1.1.21 - -``` - -### application.properties配置数据库连接信息 - -以spring.datasource.druid作为前缀 -``` -spring.datasource.druid.url=jdbc:mysql://localhost:3306/test -spring.datasource.druid.username=root -spring.datasource.druid.password= -spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver -``` - -### 配置druid连接池 -> 具体的信息可以自己查询相关的内容。 -``` -spring.datasource.druid.initialSize=10 -spring.datasource.druid.maxActive=20 -spring.datasource.druid.maxWait=60000 -spring.datasource.druid.minIdle=1 -spring.datasource.druid.timeBetweenEvictionRunsMillis=60000 -spring.datasource.druid.minEvictableIdleTimeMillis=300000 -spring.datasource.druid.testWhileIdle=true -spring.datasource.druid.testOnBorrow=true -spring.datasource.druid.testOnReturn=false -spring.datasource.druid.poolPreparedStatements=true -spring.datasource.druid.maxOpenPreparedStatements=20 -spring.datasource.druid.validationQuery=SELECT 1 -spring.datasource.druid.validation-query-timeout=500 -spring.datasource.druid.filters=stat -``` - -### 配置druid监控 - -* 在pom.xml中增加依赖 - -``` - - org.springframework.boot - spring-boot-starter-actuator - -``` - -* 在application.properties中添加druid监控配置 - -``` -spring.datasource.druid.stat-view-servlet.enabled=true -spring.datasource.druid.stat-view-servlet.url-pattern=/druid/* -spring.datasource.druid.stat-view-servlet.reset-enable=true -spring.datasource.druid.stat-view-servlet.login-username=admin -spring.datasource.druid.stat-view-servlet.login-password=admin -``` - diff --git a/Spring/Springboot/09 指标监控.md b/Spring/Springboot/09 指标监控.md new file mode 100644 index 00000000..1bffb375 --- /dev/null +++ b/Spring/Springboot/09 指标监控.md @@ -0,0 +1,294 @@ +## 1 简介 + +### 概念 +未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。 + +### 引入 + +```xml + + org.springframework.boot + spring-boot-starter-actuator + +``` +![](image/2023-11-18-19-06-53.png) + +## 2 使用 + +### 简单使用 +1. 暴露指标 +```xml +management: + endpoints: + enabled-by-default: true #暴露所有端点信息 + web: + exposure: + include: '*' #以web方式暴露 +``` +2. 访问界面 +``` +http://localhost:8080/actuator/beans +http://localhost:8080/actuator/configprops +http://localhost:8080/actuator/metrics +http://localhost:8080/actuator/metrics/jvm.gc.pause +http://localhost:8080/actuator/endpointName/detailPath +``` + +### 可视化 + + +https://github.com/codecentric/spring-boot-admin + + +### 常用指标 + +| ID | 描述 | +|------------------|--------------------------------------------------------------------------------------| +| auditevents | 暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件。 | +| beans | 显示应用程序中所有Spring Bean的完整列表。 | +| caches | 暴露可用的缓存。 | +| conditions | 显示自动配置的所有条件信息,包括匹配或不匹配的原因。 | +| configprops | 显示所有@ConfigurationProperties。 | +| env | 暴露Spring的属性ConfigurableEnvironment | +| flyway | 显示已应用的所有Flyway数据库迁移。 +需要一个或多个Flyway组件。 | +| health | 显示应用程序运行状况信息。 | +| httptrace | 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。 | +| info | 显示应用程序信息。 | +| integrationgraph | 显示Spring integrationgraph 。需要依赖spring-integration-core。 | +| loggers | 显示和修改应用程序中日志的配置。 | +| liquibase | 显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。 | +| metrics | 显示当前应用程序的“指标”信息。 | +| mappings | 显示所有@RequestMapping路径列表。 | +| scheduledtasks | 显示应用程序中的计划任务。 | +| sessions | 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 | +| shutdown | 使应用程序正常关闭。默认禁用。 | +| startup | 显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup。 | +| threaddump | 执行线程转储。 | + + +#### 最常用的Endpoint +● Health:监控状况 +● Metrics:运行时指标 +● Loggers:日志记录 + + +#### Health Endpoint +健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。 +重要的几点: +● health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告 +● 很多的健康检查默认已经自动配置好了,比如:数据库、redis等 +● 可以很容易的添加自定义的健康检查机制 + + +#### Metrics Endpoint +提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到; +● 通过Metrics对接多种监控系统 +● 简化核心Metrics开发 +● 添加自定义Metrics或者扩展已有Metrics + + +#### 管理Endpoints +1、开启与禁用Endpoints +● 默认所有的Endpoint除过shutdown都是开启的。 +● 需要开启或者禁用某个Endpoint。配置模式为 management.endpoint..enabled = true + +```java +management: + endpoint: + beans: + enabled: true +``` + +或者禁用所有的Endpoint然后手动开启指定的Endpoint + +```java +management: + endpoints: + enabled-by-default: false + endpoint: + beans: + enabled: true + health: + enabled: true +``` + + +#### 支持的暴露方式 +● HTTP:默认只暴露health和info Endpoint +● JMX:默认暴露所有Endpoint +● 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则 + + +## 3 自定义扩展 + +#### 定制 Health 信息 +```java +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +@Component +public class MyHealthIndicator implements HealthIndicator { + + @Override + public Health health() { + int errorCode = check(); // perform some specific health check + if (errorCode != 0) { + return Health.down().withDetail("Error Code", errorCode).build(); + } + return Health.up().build(); + } + +} + +构建Health +Health build = Health.down() + .withDetail("msg", "error service") + .withDetail("code", "500") + .withException(new RuntimeException()) + .build(); +``` + +配置文件 + +```sh +management: + health: + enabled: true + show-details: always #总是显示详细信息。可显示每个模块的状态信息 +``` + +```java +@Component +public class MyComHealthIndicator extends AbstractHealthIndicator { + + /** + * 真实的检查方法 + * @param builder + * @throws Exception + */ + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + //mongodb。 获取连接进行测试 + Map map = new HashMap<>(); + // 检查完成 + if(1 == 2){ +// builder.up(); //健康 + builder.status(Status.UP); + map.put("count",1); + map.put("ms",100); + }else { +// builder.down(); + builder.status(Status.OUT_OF_SERVICE); + map.put("err","连接超时"); + map.put("ms",3000); + } + + + builder.withDetail("code",100) + .withDetails(map); + + } +} +``` + + +### 定制info信息 + +编写配置文件 + +``` +info: + appName: boot-admin + version: 2.0.1 + mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值 + mavenProjectVersion: @project.version@ +``` + +编写InfoContributor + +```java +import java.util.Collections; + +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.stereotype.Component; + +@Component +public class ExampleInfoContributor implements InfoContributor { + + @Override + public void contribute(Info.Builder builder) { + builder.withDetail("example", + Collections.singletonMap("key", "value")); + } + +} +``` + +http://localhost:8080/actuator/info + + +### 定制Metrics信息 +1、SpringBoot支持自动适配的Metrics + +● JVM metrics, report utilization of: + ○ Various memory and buffer pools + ○ Statistics related to garbage collection + ○ Threads utilization + ○ Number of classes loaded/unloaded +● CPU metrics +● File descriptor metrics +● Kafka consumer and producer metrics +● Log4j2 metrics: record the number of events logged to Log4j2 at each level +● Logback metrics: record the number of events logged to Logback at each level +● Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time +● Tomcat metrics (server.tomcat.mbeanregistry.enabled must be set to true for all Tomcat metrics to be registered) +● Spring Integration metrics + +2、增加定制Metrics + +```java +class MyService{ + Counter counter; + public MyService(MeterRegistry meterRegistry){ + counter = meterRegistry.counter("myservice.method.running.counter"); + } + + public void hello() { + counter.increment(); + } +} + + +//也可以使用下面的方式 +@Bean +MeterBinder queueSize(Queue queue) { + return (registry) -> Gauge.builder("queueSize", queue::size).register(registry); +} +``` + +### 定制Endpoint + +```java +@Component +@Endpoint(id = "container") +public class DockerEndpoint { + + + @ReadOperation + public Map getDockerInfo(){ + return Collections.singletonMap("info","docker started..."); + } + + @WriteOperation + private void restartDocker(){ + System.out.println("docker restarted...."); + } + +} +``` + +场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活; +当然,这个也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes \ No newline at end of file diff --git a/Spring/Springboot/10 测试工具.md b/Spring/Springboot/10 测试工具.md deleted file mode 100644 index 1615fe70..00000000 --- a/Spring/Springboot/10 测试工具.md +++ /dev/null @@ -1,216 +0,0 @@ - -> springboot with junit4 &junit5 https://segmentfault.com/a/1190000040803747 - -## 1 概述 -### 多种测试模式 - - -* @RunWith(SpringJUnit4ClassRunner.class)启动Spring上下文环境。 -* @RunWith(MockitoJUnitRunner.class)mockito方法进行测试。对底层的类进行mock,测试速度快。 -* @RunWith(PowerMockRunner.class)powermock方法进行测试。对底层类进行mock,测试方法更全面。 - -### spring-boot-starter-test -SpringBoot中有关测试的框架,主要来源于 spring-boot-starter-test。一旦依赖了spring-boot-starter-test,下面这些类库将被一同依赖进去: -* JUnit:java测试事实上的标准。 -* Spring Test & Spring Boot Test:Spring的测试支持。 -* AssertJ:提供了流式的断言方式。 -* Hamcrest:提供了丰富的matcher。 -* Mockito:mock框架,可以按类型创建mock对象,可以根据方法参数指定特定的响应,也支持对于mock调用过程的断言。 -* JSONassert:为JSON提供了断言功能。 -* JsonPath:为JSON提供了XPATH功能。 - - - -### junit4 & junit5对比 - - -| 功能 | JUnit4 | JUnit5 | -|------------------|--------------|--------------| -| 声明一种测试方法 | @Test | @Test | -| 在当前类中的所有测试方法之前执行 | @BeforeClass | @BeforeAll | -| 在当前类中的所有测试方法之后执行 | @AfterClass | @AfterAll | -| 在每个测试方法之前执行 | @Before | @BeforeEach | -| 在每个测试方法之后执行 | @After | @AfterEach | -| 禁用测试方法/类 | @Ignore | @Disabled | -| 测试工厂进行动态测试 | NA | @TestFactory | -| 嵌套测试 | NA | @Nested | -| 标记和过滤 | @Category | @Tag | -| 注册自定义扩展 | NA | @ExtendWith | - - -### RunWith 和 ExtendWith - -在 JUnit4 版本,在测试类加 @SpringBootTest 注解时,同样要加上 @RunWith(SpringRunner.class)才生效,即: -``` -@SpringBootTest -@RunWith(SpringRunner.class) -class HrServiceTest { -... -} -``` -但在 JUnit5 中,官网告知 @RunWith 的功能都被 @ExtendWith 替代,即原 @RunWith(SpringRunner.class) 被同功能的 @ExtendWith(SpringExtension.class) 替代。但 JUnit5 中 @SpringBootTest 注解中已经默认包含了 @ExtendWith(SpringExtension.class)。 - -因此,在 JUnit5 中只需要单独使用 @SpringBootTest 注解即可。其他需要自定义拓展的再用 @ExtendWith,不要再用 @RunWith 了。 - - -### mockito - -Mockito 框架中最核心的两个概念就是 Mock 和 Stub。测试时不是真正的操作外部资源,而是通过自定义的代码进行模拟操作。我们可以对任何的依赖进行模拟,从而使测试的行为不需要任何准备工作或者不具备任何副作用。 - -1. 当我们在测试时,如果只关心某个操作是否执行过,而不关心这个操作的具体行为,这种技术称为 mock。比如我们测试的代码会执行发送邮件的操作,我们对这个操作进行 mock;测试的时候我们只关心是否调用了发送邮件的操作,而不关心邮件是否确实发送出去了。 - -2. 另一种情况,当我们关心操作的具体行为,或者操作的返回结果的时候,我们通过执行预设的操作来代替目标操作,或者返回预设的结果作为目标操作的返回结果。这种对操作的模拟行为称为 stub(打桩)。比如我们测试代码的异常处理机制是否正常,我们可以对某处代码进行 stub,让它抛出异常。再比如我们测试的代码需要向数据库插入一条数据,我们可以对插入数据的代码进行stub,让它始终返回1,表示数据插入成功。 - -### powermock - -需要手动引入测试类。依赖mockito,注意版本的对应关系。 - -```xml - - org.powermock - powermock-api-mockito2 - - - - org.powermock - powermock-module-junit4 - - -``` -## 2 使用 - -### 注解 - -@RunWith: -1. 表示运行方式,@RunWith(JUnit4TestRunner)、@RunWith(SpringRunner.class)、@RunWith(PowerMockRunner.class) 三种运行方式,分别在不同的场景中使用。 -2. 当一个类用@RunWith注释或继承一个用@RunWith注释的类时,JUnit将调用它所引用的类来运行该类中的测试而不是开发者去在junit内部去构建它。我们在开发过程中使用这个特性 - -@SpringBootTest: -1. 注解制定了一个测试类运行了Spring Boot环境。提供了以下一些特性: - 1. 当没有特定的ContextConfiguration#loader()(@ContextConfiguration(loader=...))被定义那么就是SpringBootContextLoader作为默认的ContextLoader。 - 2. 自动搜索到SpringBootConfiguration注解的文件。 - 3. 允许自动注入Environment类读取配置文件。 - 4. 提供一个webEnvironment环境,可以完整的允许一个web环境使用随机的端口或者自定义的端口。 - 5. 注册了TestRestTemplate类可以去做接口调用。 - -2. 添加这个就能取到spring中的容器的实例,如果配置了@Autowired那么就自动将对象注入。 - -### 基本测试用例 - -Springboot整合JUnit的步骤 -1. 导入测试对应的starter - -2. 测试类使用@SpringBootTest修饰 - -3.使用自动装配的形式添加要测试的对象。 - -```java -@SpringBootTest(classes = {SpringbootJunitApplication.class}) -class SpringbootJunitApplicationTests { - - @Autowired - private UsersDao users; - - @Test - void contextLoads() { - users.save(); - } - -} -``` - - -### mock单元测试 -因为单元测试不用启动 Spring 容器,则无需加 @SpringBootTest,因为要用到 Mockito,只需要自定义拓展 MockitoExtension.class 即可,依赖简单,运行速度更快。 - -可以明显看到,单元测试写的代码,怎么是被测试代码长度的好几倍?其实单元测试的代码长度比较固定,都是造数据和打桩,但如果针对越复杂逻辑的代码写单元测试,还是越划算的 -```java -@ExtendWith(MockitoExtension.class) -class HrServiceTest { - @Mock - private OrmDepartmentDao ormDepartmentDao; - @Mock - private OrmUserDao ormUserDao; - @InjectMocks - private HrService hrService; - - @DisplayName("根据部门名称,查询用户") - @Test - void findUserByDeptName() { - Long deptId = 100L; - String deptName = "行政部"; - OrmDepartmentPO ormDepartmentPO = new OrmDepartmentPO(); - ormDepartmentPO.setId(deptId); - ormDepartmentPO.setDepartmentName(deptName); - OrmUserPO user1 = new OrmUserPO(); - user1.setId(1L); - user1.setUsername("001"); - user1.setDepartmentId(deptId); - OrmUserPO user2 = new OrmUserPO(); - user2.setId(2L); - user2.setUsername("002"); - user2.setDepartmentId(deptId); - List userList = new ArrayList<>(); - userList.add(user1); - userList.add(user2); - - Mockito.when(ormDepartmentDao.findOneByDepartmentName(deptName)) - .thenReturn( - Optional.ofNullable(ormDepartmentPO) - .filter(dept -> deptName.equals(dept.getDepartmentName())) - ); - Mockito.doReturn( - userList.stream() - .filter(user -> deptId.equals(user.getDepartmentId())) - .collect(Collectors.toList()) - ).when(ormUserDao).findByDepartmentId(deptId); - - List result1 = hrService.findUserByDeptName(deptName); - List result2 = hrService.findUserByDeptName(deptName + "error"); - - Assertions.assertEquals(userList, result1); - Assertions.assertEquals(Collections.emptyList(), result2); - } -``` - - -### 集成单元测试 -还是那个方法,如果使用Spring上下文,真实的调用方法依赖,可直接用下列方式 -```java -@SpringBootTest -class HrServiceTest { - @Autowired - private HrService hrService; - - @DisplayName("根据部门名称,查询用户") - @Test - void findUserByDeptName() { - List userList = hrService.findUserByDeptName("行政部"); - Assertions.assertTrue(userList.size() > 0); - } -} -``` - - -还可以使用@MockBean、@SpyBean替换Spring上下文中的对应的Bean: -```java -@SpringBootTest -class HrServiceTest { - @Autowired - private HrService hrService; - @SpyBean - private OrmDepartmentDao ormDepartmentDao; - - @DisplayName("根据部门名称,查询用户") - @Test - void findUserByDeptName() { - String deptName="行政部"; - OrmDepartmentPO ormDepartmentPO = new OrmDepartmentPO(); - ormDepartmentPO.setDepartmentName(deptName); - Mockito.when(ormDepartmentDao.findOneByDepartmentName(ArgumentMatchers.anyString())) - .thenReturn(Optional.of(ormDepartmentPO)); - List userList = hrService.findUserByDeptName(deptName); - Assertions.assertTrue(userList.size() > 0); - } -} -``` \ No newline at end of file diff --git a/Spring/Springboot/10 高级特性.md b/Spring/Springboot/10 高级特性.md new file mode 100644 index 00000000..6ad03dee --- /dev/null +++ b/Spring/Springboot/10 高级特性.md @@ -0,0 +1,93 @@ +## 1 Profile + +### Profile功能 +为了方便多环境适配,springboot简化了profile功能。 + +application-profile功能 +● 默认配置文件 application.yaml;任何时候都会加载 +● 指定环境配置文件 application-{env}.yaml +● 激活指定环境 + ○ 配置文件激活 + ○ 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha + ■ 修改配置文件的任意值,命令行优先 +● 默认配置与环境配置同时生效 +● 同名配置项,profile配置优先 + +### @Profile条件装配功能 + +@Configuration(proxyBeanMethods = false) +@Profile("production") +public class ProductionConfiguration { + + // ... + +} + +### Profile 分组功能 + +spring.profiles.group.production[0]=proddb +spring.profiles.group.production[1]=prodmq + +使用:--spring.profiles.active=production 激活 + +## 2 外部化配置 + +### 配置文件优先级 + +https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config + +1. Default properties (specified by setting SpringApplication.setDefaultProperties). +2. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins. +3. Config data (such as application.properties files) +4. A RandomValuePropertySource that has properties only in random.*. +5. OS environment variables. +6. Java System properties (System.getProperties()). +7. JNDI attributes from java:comp/env. +8. ServletContext init parameters. +9. ServletConfig init parameters. +10. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property). +11. Command line arguments. +12. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application. +13. @TestPropertySource annotations on your tests. +14. Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is active. + + +常用:Java属性文件、YAML文件、环境变量、命令行参数; + +### 配置文件的路径 + +(1) classpath 根路径 +(2) classpath 根路径下config目录 +(3) jar包当前目录 +(4) jar包当前目录的config目录 +(5) /config子目录的直接子目录 + + +### 配置文件加载顺序: +1.  当前jar包内部的application.properties和application.yml +2.  当前jar包内部的application-{profile}.properties 和 application-{profile}.yml +3.  引用的外部jar包的application.properties和application.yml +4.  引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml + +指定环境优先,外部优先,后面的可以覆盖前面的同名配置项 + + +## 3 自定义starter + +![](image/2023-11-18-20-10-32.png) + +starter-pom引入 autoconfigurer 包 + +● autoconfigure包中配置使用 META-INF/spring.factories 中 EnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类 +● 编写自动配置类 xxxAutoConfiguration -> xxxxProperties + ○ @Configuration + ○ @Conditional + ○ @EnableConfigurationProperties + ○ @Bean + ○ ...... + +引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项 + +自定义starter +atguigu-hello-spring-boot-starter(启动器) +atguigu-hello-spring-boot-starter-autoconfigure(自动配置包) \ No newline at end of file diff --git a/Spring/Springboot/11 启动过程.md b/Spring/Springboot/11 启动过程.md new file mode 100644 index 00000000..26288aa5 --- /dev/null +++ b/Spring/Springboot/11 启动过程.md @@ -0,0 +1,311 @@ + +## 1 springboot的启动过程 +springcontext.run到底干了什么 + +![](image/2023-01-09-10-47-27.png) + + +SpringApplication.run()到底干了什么 + +### 服务构建 + +调用SpringApplication的静态run方法。通过一系列配置创建SpringApplication类。 +1. 初始化资源加载器 +2. 初始化服务类型 +3. 初始化spring.factories中定义的初始化类。包括Initializer和Listener +4. 找到启动类 + +![](image/2023-01-09-10-56-25.png) + +```java + /** + * Create a new {@link SpringApplication} instance. The application context will load + * beans from the specified primary sources (see {@link SpringApplication class-level} + * documentation for details). The instance can be customized before calling + * {@link #run(String...)}. + * @param resourceLoader the resource loader to use + * @param primarySources the primary bean sources + * @see #run(Class, String[]) + * @see #setSources(Set) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { + this.resourceLoader = resourceLoader; + Assert.notNull(primarySources, "PrimarySources must not be null"); + this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); + this.webApplicationType = WebApplicationType.deduceFromClasspath(); + this.bootstrapRegistryInitializers = new ArrayList<>( + getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); + setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); + setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); + this.mainApplicationClass = deduceMainApplicationClass(); + } +``` + + +### 环境准备 +调用SpringApplicationContext的run方法。 +1. 加载bootstrapContext上下文 +2. 获取并注册监听器。 +3. 加载环境变量,并发布环境变量加载完成的事件。(通过观察者模式) + + +![](image/2023-01-09-11-25-19.png) + +```java + /** + * Run the Spring application, creating and refreshing a new + * {@link ApplicationContext}. + * @param args the application arguments (usually passed from a Java main method) + * @return a running {@link ApplicationContext} + */ + public ConfigurableApplicationContext run(String... args) { + long startTime = System.nanoTime(); + DefaultBootstrapContext bootstrapContext = createBootstrapContext(); + ConfigurableApplicationContext context = null; + configureHeadlessProperty(); + SpringApplicationRunListeners listeners = getRunListeners(args); + listeners.starting(bootstrapContext, this.mainApplicationClass); + try { + ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); + ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); + configureIgnoreBeanInfo(environment); + Banner printedBanner = printBanner(environment); + context = createApplicationContext(); + context.setApplicationStartup(this.applicationStartup); + prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); + refreshContext(context); + afterRefresh(context, applicationArguments); + Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); + if (this.logStartupInfo) { + new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup); + } + listeners.started(context, timeTakenToStartup); + callRunners(context, applicationArguments); + } +``` + +### 容器创建 +在run方法中创建容器上下文SpringApplicationContext + + +![](image/2023-01-09-11-39-47.png) + +1. 默认创建AnnotationConfigServletWebServerApplicationContext。在该类中调用两个注解处理方法。 + +```java + public AnnotationConfigServletWebServerApplicationContext() { + this.reader = new AnnotatedBeanDefinitionReader(this); + this.scanner = new ClassPathBeanDefinitionScanner(this); + } +``` +2. 构建conttext。加载Initializer,注册启动参数,加载postProcess. + +```java + private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, + ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, + ApplicationArguments applicationArguments, Banner printedBanner) { + context.setEnvironment(environment); + postProcessApplicationContext(context); + applyInitializers(context); + listeners.contextPrepared(context); + bootstrapContext.close(context); + if (this.logStartupInfo) { + logStartupInfo(context.getParent() == null); + logStartupProfileInfo(context); + } + // Add boot specific singleton beans + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + beanFactory.registerSingleton("springApplicationArguments", applicationArguments); + if (printedBanner != null) { + beanFactory.registerSingleton("springBootBanner", printedBanner); + } + if (beanFactory instanceof AbstractAutowireCapableBeanFactory) { + ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences); + if (beanFactory instanceof DefaultListableBeanFactory) { + ((DefaultListableBeanFactory) beanFactory) + .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); + } + } + if (this.lazyInitialization) { + context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); + } + context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context)); + // Load the sources + Set sources = getAllSources(); + Assert.notEmpty(sources, "Sources must not be empty"); + load(context, sources.toArray(new Object[0])); + } +``` +3. 发布资源监听事件 +```java + listeners.contextLoaded(context); + +``` + +### 填充容器——自动装配 + +![](image/2023-01-09-11-40-17.png) +1. refreshContext(conext) +2. 发布启动完成事件,调用自定义实现的runner接口。 + + + + +## 2 SpringBoot初始化过程 +> Spring原理【Spring注解】、SpringMVC原理、自动配置原理、SpringBoot原理 + + +### 创建 SpringApplication +1. 保存一些信息。 +2. 判定当前应用的类型。ClassUtils。Servlet +3. bootstrappers:初始启动引导器(List):去spring.factories文件中找 org.springframework.boot.Bootstrapper +4. 找 ApplicationContextInitializer;去spring.factories找 ApplicationContextInitializer + 1. List> initializers +5. 找 ApplicationListener ;应用监听器。去spring.factories找 ApplicationListener + 1. List> listeners +### 运行 SpringApplication +1. StopWatch记录应用的启动时间 +2. 创建引导上下文(Context环境)createBootstrapContext() +3. 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置 +4. 让当前应用进入headless模式。java.awt.headless +5. 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】 + 1. getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener. +6. 遍历 SpringApplicationRunListener 调用 starting 方法; + 1. 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。 +7. 保存命令行参数;ApplicationArguments +8. 准备环境 prepareEnvironment(); + 1. 返回或者创建基础环境信息对象。StandardServletEnvironment + 2. 配置环境信息对象。 + 3. 读取所有的配置源的配置属性值。 + 4. 绑定环境信息 + 5. 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成 +9. 创建IOC容器(createApplicationContext()) + 1. 根据项目类型(Servlet)创建容器, + 2. 当前会创建 AnnotationConfigServletWebServerApplicationContext +10. 准备ApplicationContext IOC容器的基本信息 prepareContext() + 1. 保存环境信息 + 2. IOC容器的后置处理流程。 + 3. 应用初始化器;applyInitializers; + 1. 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能 + 2. 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared + 4. 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded; +11. 刷新IOC容器。refreshContext + 1. 创建容器中的所有组件(Spring注解) + 2. 容器刷新完成后工作?afterRefresh +12. 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started +13. 调用所有runners;callRunners() + 1. 获取容器中的 ApplicationRunner + 2. 获取容器中的 CommandLineRunner + 3. 合并所有runner并且按照@Order进行排序 + 4. 遍历所有的runner。调用 run 方法 +14. 如果以上有异常,调用Listener 的 failed +15. 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running +16. running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed + + +## 3 SpringBoot扩展点 + +### 扩展点原理 +扩展点的服务发现机制 +1. 利用SPI服务发现机制,完成在Context初始化之前的扩展。 +2. 利用@注解和接口机制,完成IOC容器初始化之后的扩展。 + +扩展点观察者模式 +1. 定义Lisenter。通过回调接口监听SpringBoot启动过程。 + +主要的扩展点如下 +1. ApplicationContextInitializer +1. ApplicationListener +1. SpringApplicationRunListener +1. ApplicationRunner +1. CommandLineRunner + +### 扩展点BootStrapper +```java +public interface Bootstrapper { + + /** + * Initialize the given {@link BootstrapRegistry} with any required registrations. + * @param registry the registry to initialize + */ + void intitialize(BootstrapRegistry registry); + +} +``` + +### 扩展点ContextInitializer +![](image/2023-11-18-20-24-19.png) + + +### 扩展点RunListener +![](image/2023-11-18-20-24-27.png) +![](image/2023-11-18-20-24-40.png) + + +### 扩展点ApplicationRunner + +```java +@FunctionalInterface +public interface ApplicationRunner { + + /** + * Callback used to run the bean. + * @param args incoming application arguments + * @throws Exception on error + */ + void run(ApplicationArguments args) throws Exception; + +} +``` + + +### 扩展点CommandLineRunner + +```java +@FunctionalInterface +public interface CommandLineRunner { + + /** + * Callback used to run the bean. + * @param args incoming main method arguments + * @throws Exception on error + */ + void run(String... args) throws Exception; + +} +``` + +## 4 springApplication创建的方法 + +### 1.通过类的静态方法直接创建 + +```java +SpringApplication.run(ApplicationConfiguration.class,args); +``` + +### 2.通过自定义SpringApplication创建 + +```java +SpringApplication springApplication = new SpringApplication(ApplicationConfiguration.class); //这里也是传入配置源,但也可以不传 +springApplication.setWebApplicationType(WebApplicationType.NONE); //指定服务类型 可以指定成非Web应用和SERVLET应用以及REACTIVE应用 +springApplication.setAdditionalProfiles("prod"); //prodFiles配置 +Set sources = new HashSet<>(); //创建配置源 +sources.add(ApplicationConfiguration.class.getName()); //指定配置源 +springApplication.setSources(sources); //设置配置源,注意配置源可以多个 +ConfigurableApplicationContext context = springApplication.run(args); //运行SpringApplication 返回值为服务上下文对象 +context.close(); //上下文关闭 +``` + + +### 3.通过Builder工厂模式 +> 只是一种初始化对象的方法。 + +```java +ConfigurableApplicationContext context = new SpringApplicationBuilder(ApplicationConfiguration.class)//这里也是传入配置源,但也可以不传 + .web(WebApplicationType.REACTIVE) + .profiles("java7") + .sources(ApplicationConfiguration.class) //可以多个Class + .run(); +context.close(); //上下文关闭 +``` \ No newline at end of file diff --git a/Spring/Springboot/11 源码解析(一)Bean生命周期.md b/Spring/Springboot/12 Bean生命周期.md similarity index 100% rename from Spring/Springboot/11 源码解析(一)Bean生命周期.md rename to Spring/Springboot/12 Bean生命周期.md diff --git a/Spring/Springboot/hello.c b/Spring/Springboot/hello.c deleted file mode 100644 index b1468bbc..00000000 --- a/Spring/Springboot/hello.c +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int main(int argc, char **argv){ - - int i = 10; - char *b = &i; - -} \ No newline at end of file diff --git a/Spring/Springboot/image/2023-11-18-16-28-24.png b/Spring/Springboot/image/2023-11-18-16-28-24.png new file mode 100644 index 00000000..424072fe Binary files /dev/null and b/Spring/Springboot/image/2023-11-18-16-28-24.png differ diff --git a/Spring/Springboot/image/2023-11-18-16-53-56.png b/Spring/Springboot/image/2023-11-18-16-53-56.png new file mode 100644 index 00000000..8a7eaae5 Binary files /dev/null and b/Spring/Springboot/image/2023-11-18-16-53-56.png differ diff --git a/Spring/Springboot/image/2023-11-18-18-53-47.png b/Spring/Springboot/image/2023-11-18-18-53-47.png new file mode 100644 index 00000000..e0535a2c Binary files /dev/null and b/Spring/Springboot/image/2023-11-18-18-53-47.png differ diff --git a/Spring/Springboot/image/2023-11-18-18-58-17.png b/Spring/Springboot/image/2023-11-18-18-58-17.png new file mode 100644 index 00000000..4e8b5526 Binary files /dev/null and b/Spring/Springboot/image/2023-11-18-18-58-17.png differ diff --git a/Spring/Springboot/image/2023-11-18-19-06-53.png b/Spring/Springboot/image/2023-11-18-19-06-53.png new file mode 100644 index 00000000..f8b27c01 Binary files /dev/null and b/Spring/Springboot/image/2023-11-18-19-06-53.png differ diff --git a/Spring/Springboot/image/2023-11-18-20-10-32.png b/Spring/Springboot/image/2023-11-18-20-10-32.png new file mode 100644 index 00000000..88920976 Binary files /dev/null and b/Spring/Springboot/image/2023-11-18-20-10-32.png differ diff --git a/Spring/Springboot/image/2023-11-18-20-24-19.png b/Spring/Springboot/image/2023-11-18-20-24-19.png new file mode 100644 index 00000000..52d9824f Binary files /dev/null and b/Spring/Springboot/image/2023-11-18-20-24-19.png differ diff --git a/Spring/Springboot/image/2023-11-18-20-24-27.png b/Spring/Springboot/image/2023-11-18-20-24-27.png new file mode 100644 index 00000000..69b6a90e Binary files /dev/null and b/Spring/Springboot/image/2023-11-18-20-24-27.png differ diff --git a/Spring/Springboot/image/2023-11-18-20-24-40.png b/Spring/Springboot/image/2023-11-18-20-24-40.png new file mode 100644 index 00000000..1f40a743 Binary files /dev/null and b/Spring/Springboot/image/2023-11-18-20-24-40.png differ diff --git a/Spring/Springboot/image/2023-11-18-20-31-33.png b/Spring/Springboot/image/2023-11-18-20-31-33.png new file mode 100644 index 00000000..7a0ba1f7 Binary files /dev/null and b/Spring/Springboot/image/2023-11-18-20-31-33.png differ diff --git a/Spring/Springboot/spring创建的方式.md b/Spring/Springboot/spring创建的方式.md index 9b2f1c2f..e69de29b 100644 --- a/Spring/Springboot/spring创建的方式.md +++ b/Spring/Springboot/spring创建的方式.md @@ -1,33 +0,0 @@ -# springApplication创建的方法 - -### 1.通过类的静态方法直接创建 - -```java -SpringApplication.run(ApplicationConfiguration.class,args); -``` - -### 2.通过自定义SpringApplication创建 - -```java -SpringApplication springApplication = new SpringApplication(ApplicationConfiguration.class); //这里也是传入配置源,但也可以不传 -springApplication.setWebApplicationType(WebApplicationType.NONE); //指定服务类型 可以指定成非Web应用和SERVLET应用以及REACTIVE应用 -springApplication.setAdditionalProfiles("prod"); //prodFiles配置 -Set sources = new HashSet<>(); //创建配置源 -sources.add(ApplicationConfiguration.class.getName()); //指定配置源 -springApplication.setSources(sources); //设置配置源,注意配置源可以多个 -ConfigurableApplicationContext context = springApplication.run(args); //运行SpringApplication 返回值为服务上下文对象 -context.close(); //上下文关闭 -``` - - -### 3.通过Builder工厂模式 -> 只是一种初始化对象的方法。 - -```java -ConfigurableApplicationContext context = new SpringApplicationBuilder(ApplicationConfiguration.class)//这里也是传入配置源,但也可以不传 - .web(WebApplicationType.REACTIVE) - .profiles("java7") - .sources(ApplicationConfiguration.class) //可以多个Class - .run(); -context.close(); //上下文关闭 -``` \ No newline at end of file diff --git a/设计模式/4.7 观察者.md b/设计模式/4.7 观察者.md index de5ef7e5..bf8db1a0 100644 --- a/设计模式/4.7 观察者.md +++ b/设计模式/4.7 观察者.md @@ -11,9 +11,10 @@ > * 在UI框架中就是Listener,事件监听机制和响应机制 > * 在Spring框架中也有事件监听模型。 > * 在web网站开发中,被称为响应式编程。 -> * 在微服务中就是注册中心的发布订阅过程 +> 服务发现机制。以下并不是观察者模式的范畴,是一种更加宏观的机制,可能使用了观察者模式去发现。 +> * javaSPI。服务提供者接口 +> * 在微服务中就是注册中心的发布订阅过程。发布者订阅者、提供者消费者。 > * 在消息中间件中就是发布订阅模式。 -> * **意图**