尚学堂JAVA高级学习笔记


文章目录



写在前面

学习链接:​​Java 视频教程全集​

课件链接:​​Java课件​

声明:全是本人边学习边手打的,希望对大家有帮助。

第4章 正则表达式

  • 文本的复杂处理
  • regular expression
  • 开发中使用正则表达式的流程:
  • 分析所有匹配的数据,写出测试用的典型数据
  • 在工具软件中进行匹配测试
  • 在程序中调用通过测试的正则表达式
  • regexbuddy工具

1. 正则表达式语法

  • 普通字符:匹配与之相同的一个字符
  • 简单的转义字符:
    尚学堂JAVA高级学习笔记_2/2_java
  • 标准字符集合
  • 能够与“多种字符”匹配的表达式
  • 注意区分带小写,大写是相反的意思(比如\d是匹配一个数字,\D就是匹配非数字)
  • 小数点不能匹配换行符\n
  • [\s\S]用来匹配任意字符,包括换行符
  • 自定义字符集合
  • []方括号匹配方式,能够匹配方括号中任意一个字符
  • 正则表达式的特殊符号,被包含到中括号中,则失去特殊意义,除了^,-之外
  • 标准字符集合,除小数点外,如果被包含于中括号,自定义字符集合将包含该集合。比如:[\d.\-+]将匹配:数字、小数点、+、-
  • 量词 | Quantifier
  • 修饰匹配次数的特殊符号
    尚学堂JAVA高级学习笔记_2/2_sql_02
    \d\d{6} != {\d\d}{6}
  • 匹配次数中的贪婪模式(匹配字符越多越好,默认!)
  • 匹配次数中的非贪婪模式(匹配字符越少越好,修饰匹配次数的特殊符号后再加上一个“?”号)例:\d{2,3}?
  • 字符边界(零宽)
  • 本组标记匹配的不是字符而是位置,符合某 种条件的位置
  • \b匹配这样一个位置:前面的字符和后面的字符不全是\w
    尚学堂JAVA高级学习笔记_2/2_笔记_03
  • gaoqi\b测试:
    尚学堂JAVA高级学习笔记_2/2_java_04
  • \bgaoqi\b测试:
    尚学堂JAVA高级学习笔记_2/2_java_05
  • IGNORECASE:忽略大小写模式
  • 匹配时忽略大小写
  • 默认情况下,正则表达式是要区分大小写的
  • SINGLELINE:单行模式
  • 整个文本看作一个字符串,只有一个开头,一个结尾。
  • 使小数点“.”可以匹配包含换行符(\n)在内的任意字符。
  • MULTILINE:多行模式
  • 每行都是一个字符串,都有开头和结尾
  • 在制定了MULTILINE之后,如果需要仅匹配字符串开始和结束位置,可以使用**\A和\Z**
  • 选择符和分组
    尚学堂JAVA高级学习笔记_2/2_java_06
  • 反向引用(\nnm)
  • 每一对()会分配一个编号,使用()的捕获根据左括号的顺序从1开始自动编号。
  • 通过反向应用,可以对分组已捕获的字符串进行应用。
  • 预搜索(零宽断言)
  • 只进行子表达式的匹配,匹配内容不计入最终的匹配结果,是零宽度
  • 这个位置应该符合某个条件。判断当前位置的前后字符,是否符合指定的条件,但不匹配前后的字符。是对位置的匹配。
  • 正则表达式匹配过程中,如果子表达式匹配到的是字符的内容,而非位置,并被保存到最终的匹配结果中,那么就认为这个子表达式是占有字符的;如果子表达式匹配的仅仅是位置,或者匹配的内容并不保存到最终的匹配结果中,那么就认为这个子表达式是零宽度的。占有字符还是零宽度,是针对匹配的内容是否保存到最终的匹配结果中而言的。
    尚学堂JAVA高级学习笔记_2/2_笔记_07

2. 正则表达式的练习

  • 练习1
    尚学堂JAVA高级学习笔记_2/2_mysql_08
  • 解答1:(0\d{2,3}-\d{7,8})|(1[35789]\d{9})
  • 练习2
    尚学堂JAVA高级学习笔记_2/2_sql_09
  • 解答2:[\w\-]+@[a-z0-9A-Z]+(\.[A-Za-z]{2,4}){1,2}

3. 常用正则表达式列表

尚学堂JAVA高级学习笔记_2/2_笔记_10

4. JAVA程序中使用正则表达式

  • 开发环境
  • 开发环境和文本编译器中使用正则
  • 数据库中也可以使用正则
  • JAVA相关类位于java.util.regex包下面
  • 类Pattern:
  • 正则表达式的编译表现形式
  • 建立正则表达式,并启用相应模式
  • ​Pattern p = Pattern.compile(r, int);​
  • 类Matcher:
  • 通过解释Pattern对character sequence执行匹配操作的引擎
  • 匹配str字符串
  • ​Matcher m = p.matcher(str);​
  • 测试
  • 匹配整个正则表达式
    package com.sxt.regex; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Demo01.java * @time: 2020/3/3 14:10 * @desc: | */ public class Demo01 { public static void main(String[] args){ // 在这个字符串:asdfsadf2323,是否符合制定的正则表达式:\w+ Pattern p = Pattern.compile("\\w+"); // 创建Matcher对象 Matcher m = p.matcher("asdfsadf@@2323"); // 尝试将整个字符序列与该模式匹配 // boolean yo = m.matches(); // 该方法扫描输入的序列,查找与该模式匹配的下一个子序列 while(m.find()){ // group()和group(0)都是匹配整个表达式的子字符串 System.out.println(m.group()); System.out.println(m.group(0)); } } }
  • 测试正则表达式分组的处理
    package com.sxt.regex; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 测试正则表达式分组的处理 */ public class Demo02 { public static void main(String[] args) { // 在这个字符串:asdfsadf2323,是否符合制定的正则表达式:\w+ Pattern p = Pattern.compile("([a-z]+)([0-9]+)"); // 创建Matcher对象 Matcher m = p.matcher("asdfsa12**asd233**dsd11"); // 尝试将整个字符序列与该模式匹配 while (m.find()) { // group()和group(0)都是匹配整个表达式的子字符串 System.out.println("start---"); System.out.println("满足整个表达式的子字符串:"); System.out.println(m.group()); System.out.println("满足第1个括号中表达式的字符串:"); System.out.println(m.group(1)); System.out.println("满足第2个括号中表达式的字符串:"); System.out.println(m.group(2)); } } }
  • 测试正则表达对象替换操作
    package com.sxt.regex; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 测试正则表达对象替换操作 */ public class Demo03 { public static void main(String[] args) { // 在这个字符串:asdfsadf2323,是否符合制定的正则表达式:\w+ Pattern p = Pattern.compile("[0-9]"); // 创建Matcher对象 Matcher m = p.matcher("asdfsa12**asd233**dsd11"); // 替换 String newStr = m.replaceAll("#"); System.out.println(newStr); } }
  • 测试正则表达对象分割字符串的操作
    package com.sxt.regex; import java.util.Arrays; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 测试正则表达对象分割字符串的操作 */ public class Demo04 { public static void main(String[] args) { String str = "asdfsa12asd233dsd11"; // 切割 String[] arrs = str.split("\\d+"); System.out.println(Arrays.toString(arrs)); } }

5. 手写网络爬虫

package com.sxt.regex;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* @author: Li Tian
* @contact: litian_cup@163.com
* @software: IntelliJ IDEA
* @file: WebSpider.java
* @time: 2020/3/4 17:29
* @desc: |网络爬虫取数据
*/

public class WebSpider {
public static void main(String[] args) {
String url = "http://www.163.com";
String destStr = getURLContent(url);

// 取到的超链接的整个内容
// Pattern p = Pattern.compile("<a[\\s\\S]+?</a>");
// 取到的超链接的地址
// Pattern p = Pattern.compile("href=\"(.+?)\"");
// 注意:上述?是非贪婪模式
// Matcher m = p.matcher(destStr);
// while (m.find()) {
// System.out.println(m.group());
// System.out.println("-----");
// System.out.println(m.group(1));
// }

List<String> result = getMatherSubstrs(destStr, "href=\"(http://[\\w\\s./]+?)\"");
for (String temp : result) {
System.out.println(temp);
}
}

public static String getURLContent(String loc) {
/*获得url对应的网页源码内容*/
StringBuilder sb = new StringBuilder();
try {
URL url = new URL(loc);
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), Charset.forName("gbk")));
String temp = "";
while ((temp = reader.readLine()) != null) {
sb.append(temp);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}

public static List<String> getMatherSubstrs(String destStr, String regexStr) {
// 取到的超链接地址
Pattern p = Pattern.compile(regexStr);
Matcher m = p.matcher(destStr);
List<String> result = new ArrayList<>();
while (m.find()) {
result.add(m.group(1));
}
return result;
}
}

第5章 JDBC

1. Mysql引入

  • 常用命令行操作
    尚学堂JAVA高级学习笔记_2/2_sql_11

2. JDBC简述

  • JDBC(Java Database Connection)为java开发者使用数据库提供了统一的编程接口,它由一组java类和接口组成。是java程序与数据库系统通信的标准api。JDBC API使得开发人员可以使用纯java的方式来连接数据库,并执行操作。
  • 访问数据库流程
    尚学堂JAVA高级学习笔记_2/2_java_12
  • Driver接口
  • Driver接口由数据库厂家提供,对于java开发者而言,只需要使用Driver接口就可以了。
  • 在编程中要连接数据库,必须先装载特定厂商的数据库驱动程序。不同的数据库有不同的装载方法。
  • 驱动:就是各个数据库厂商实现的Sun公司提出的JDBC接口。即对Connection等接口的实现类的jar文件。
  • 装载MySQL驱动
  • ​Class.forName("com.mysql.jdbc.Driver");​
  • 装载Oracle驱动
  • ​Class.forName("oracle.jdbc.driver.OracleDriver")​
  • DriverManager接口
  • DriverManager是JDBC的管理层,作用于用户和驱动程序之间。
  • DriverManager跟踪可用的驱动程序,并在数据库和相应的驱动程序之间建立连接。
  • Connection接口
  • Connection与特定数据库的连接(会话),在连接上下文中执行SQL语句并返回结果。
  • DriverManager的getConnection()方法建立在JDBC URL中定义的数据库Connection连接上。
  • 连接Mysql数据:
  • ​Connection con = DriverManager.getConnection("jdbc:mysql://host:port/database", "user", "password");​
  • 连接Oracle数据库:
  • ​Connection con = DriverManager.gtConnection("jdbc:oracle:thin:@host:port:database", "user", "password");​
  • JDBC详细操作
  • 灵活指定SQL语句中的变量:PreparedStatement
  • 对存储过程进行调用:CallableStatement
  • 运用事务处理:Transaction
  • 批处理:Batch,对于大量的批处理,建议使用Statement,因为PreparedStatement的预编译空间有限,当数据量特别大时,会发生异常。
  • 连接测试
  • 报错参考连接:解决方案
    package com.sxt.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Demo01.java * @time: 2020/3/5 12:48 * @desc: | 测试跟数据库建立连接 * 如果报错:参考连接:javascript:void(0) */ public class Demo01 { public static void main(String[] args) { try { // 加载驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); long start = System.currentTimeMillis(); // 建立连接(连接对象内部其实包含了Socket对象,是一个远程的连接。比较耗时!这是Connection对象管理的一个要点!) // 真正开发中,为了提高效率,都会使用连接池来管理连接对象! Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456"); long end = System.currentTimeMillis(); System.out.println(conn); System.out.println("建立连接耗时:" + (end - start) + "ms毫秒"); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } } }

3. JDBC常用接口

  • Statement接口
  • 用于执行静态SQL语句并返回它所生成结果的对象。
  • 三种Statement类:
  • Statement:由createStatement创建,用于发送简单的SQL语句。(不带参数的)
  • PreparedStatement:继承自Statement接口,由prepareStatement创建,用于发送含有一个或多个输入参数的sql语句。PreparedStatement对象比Statement对象的效率更高,并且可以防止SQL注入。我们一般都用PreparedStatement
  • CallableStatement:集成自PreparedStatement。由方法prePareCall创建,用于调用存储过程。
  • 常用的Statement方法:
  • execute():运行语句,返回是否有结果集。
  • executeQuery():运行select语句,返回ResultSet结果集。
  • executeUpadate():运行insert/update/delete操作,返回更新的行数。
  • Statement测试执行sql语句以及sql注入问题
    package com.sxt.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Demo02.java * @time: 2020/3/5 12:48 * @desc: | 测试执行sql语句以及sql注入问题 */ public class Demo02 { public static void main(String[] args) { try { // 加载驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); // 建立连接(连接对象内部其实包含了Socket对象,是一个远程的连接。比较耗时!这是Connection对象管理的一个要点!) // 真正开发中,为了提高效率,都会使用连接池来管理连接对象! Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456"); Statement stmt = conn.createStatement(); String sql = "insert into t_user (username, pwd, regTime) values ('赵六', 6666, now())"; stmt.execute(sql); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } } }
  • sql注入问题:若要根据id删除一行记录,很容易出现数据库危险,比如要删除id=5的记录,传入的时候为id = 5 or 1 = 1,最终导致数据库都被删除。
  • 测试PreparedStatement的用法
    package com.sxt.jdbc; import java.sql.*; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Demo01.java * @time: 2020/3/5 12:48 * @desc: | 测试PreparedStatement的基本用法 */ public class Demo03 { public static void main(String[] args) { try { // 加载驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456"); // ?是占位符 String sql = "insert into t_user (username, pwd, regTime) values (?, ?, ?)"; PreparedStatement ps = conn.prepareStatement(sql); // 参数索引是从1开始计算,而不是0 // ps.setString(1, "傻瓜"); // ps.setString(2, "12345"); // 还可以不管类型直接setObject // ps.setObject(1, "傻瓜2"); // ps.setObject(2, "12344"); // 设置时间:注意该时间的格式应该是java.sql.Date ps.setObject(1, "傻瓜3"); ps.setObject(2, "12343"); ps.setObject(3, new java.sql.Date(System.currentTimeMillis())); System.out.println("插入一行记录"); // 返回是否有结果集 // ps.execute(); // 返回更新的行数 int count = ps.executeUpdate(); System.out.println(count); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } } }
  • 关闭顺序:resultset–>statement–>connection
  • 测试ResultSet结果集的用法
    package com.sxt.jdbc; import java.sql.*; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 测试ResultSet结果集的用法 * 记得要关闭打开的接口 */ public class Demo04 { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 加载驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456"); // ?是占位符 String sql = "select id, username, pwd from t_user where id>?"; ps = conn.prepareStatement(sql); // 把id>2的记录都取出来 ps.setObject(1, 2); rs = ps.executeQuery(); while (rs.next()) { // 数字代表哪一列 System.out.println(rs.getInt(1) + "-->" + rs.getString(2) + "-->" + rs.getString(3)); } } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } finally { // 一定要将三个try catch分开写 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
  • Batch批处理,尽量使用Statement而不是PreparedStatement
    package com.sxt.jdbc; import java.sql.*; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 批处理 */ public class Demo05 { public static void main(String[] args) { Connection conn = null; Statement stmt = null; ResultSet rs = null; try { // 加载驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456"); // 设为手动提交 conn.setAutoCommit(false); long start = System.currentTimeMillis(); stmt = conn.createStatement(); for (int i = 0; i < 20000; i++) { stmt.addBatch("insert into t_user (username, pwd, regTime) values ('li'" + ", 666666, now())"); stmt.executeBatch(); } // 提交事务 conn.commit(); long end = System.currentTimeMillis(); System.out.println("插入20000条数据,耗时(毫秒):" + (end - start)); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } finally { // 一定要将三个try catch分开写 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }

4. 事务

  • 事务的基本概念:一组要么同时执行成功,要么同时执行失败的SQL语句。是数据库操作的一个执行单元。
  • 事务开始于:
  • 连接到数据库上,并执行一条DML语句(INSERT、UPDATE或DELETE)
  • 前一个事务结束后,又输入了另外一条DML语句
  • 事务结束于:
  • 执行COMMIT或ROLLBACK语句
  • 执行一条DDL语句,如CREATE TABLE语句;这种情况下,会自动执行COMMIT语句
  • 执行一条DCL语句,例如GRANT语句;这种情况下,会自动执行COMMIT语句
  • 断开与数据库的连接
  • 执行了一条DML语句,该语句却失败了;在这种情况下,会为这个无效的DML语句执行ROLLBACK语句
  • 事务的四大特性(ACID)
  • atomicity(原子性):表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败
  • consistency(一致性):表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前的状态
  • isolation(隔离性):事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。
  • 事务隔离级别从低到高:
  • 读取未提交(Read Uncommitted)
  • 读取已提交(Read Committed)
  • 可重复读(Repeatable Read)
  • 序列化(Serializable)
  • durability(持久性):持久性事务完成之后,它对系统的影响是永久的
  • 测试事务的基本用法
    package com.sxt.jdbc; import java.sql.*; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 测试事务的基本用法 */ public class Demo06 { public static void main(String[] args) { Connection conn = null; PreparedStatement ps1 = null; PreparedStatement ps2 = null; try { // 加载驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456"); // JDBC默认是自动提交 conn.setAutoCommit(false); ps1 = conn.prepareStatement("insert into t_user (username, pwd) values (?, ?)"); ps1.setObject(1, "狗子"); ps1.setObject(2, "111"); ps1.execute(); System.out.println("插入一个用户1"); Thread.sleep(6000); ps2 = conn.prepareStatement("insert into t_user (username, pwd) values (?, ?, ?)"); ps2.setObject(1, "狗子2"); ps2.setObject(2, "111"); ps2.execute(); System.out.println("插入一个用户2"); conn.commit(); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { // 一定要将三个try catch分开写 if (ps1 != null) { try { ps1.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps2 != null) { try { ps2.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }

5. 时间处理

  • 时间类型
  • java.util.Date
  • 子类:java.sql.Date:表示年月日
  • 子类:java.sql.Time:表示时分秒
  • 子类:java.sql.Timestamp:表示年月日时分秒
  • 日期比较处理
  • 插入随机日期
  • 取出指定日期范围的记录
  • Date、Timestamp比较和插入随机日期
    package com.sxt.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Random; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 测试时间处理(java.sql.Date, java.sql.Time, java.sql.Timestamp) */ public class Demo07 { public static void main(String[] args) { Connection conn = null; PreparedStatement ps1 = null; try { // 加载驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456"); for (int i = 0; i < 1000; i++) { ps1 = conn.prepareStatement("insert into t_user (username, pwd, regTime, lastLoginTime) values (?, ?, ?, ?)"); ps1.setObject(1, "狗子" + i); ps1.setObject(2, "111"); // 定义随机数 int rand = 10000000 + new Random().nextInt(1000000000); java.sql.Date date = new java.sql.Date(System.currentTimeMillis() - rand); ps1.setDate(3, date); // 如果需要插入制定日期,可以使用Calendar或DateFormat java.sql.Timestamp stamp = new java.sql.Timestamp(System.currentTimeMillis()); ps1.setTimestamp(4, stamp); ps1.execute(); } } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } finally { // 一定要将三个try catch分开写 if (ps1 != null) { try { ps1.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
  • 取出指定日期范围的记录
    package com.sxt.jdbc; import java.sql.*; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 测试时间处理,取出指定时间段的数据 */ public class Demo08 { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 加载驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456"); // 选择满足regTime条件的记录,Timestamp格式同理,把java.sql.Date改成java.sql.Timestamp即可getDate改成getTimestamp ps = conn.prepareStatement("select * from t_user where regTime>? and regTime<?"); java.sql.Date start = new java.sql.Date(str2Date("2020-3-1 10:23:45")); java.sql.Date end = new java.sql.Date(str2Date("2020-3-3 10:23:45")); ps.setObject(1, start); ps.setObject(2, end); rs = ps.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("id") + "-->" + rs.getString("username") + "-->" + rs.getDate("regTime")); } } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } finally { // 一定要将三个try catch分开写 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } public static long str2Date(String dateStr){ /*将字符串代表的日期转为long数字(格式:yyyy-MM-dd hh:mm:ss)*/ DateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); try { return format.parse(dateStr).getTime(); } catch (ParseException e) { e.printStackTrace(); return 0; } } }

6. 大对象操作

6.1 CLOB文本大对象操作

  • Character Large Object
  • 用于存储大量的文本数据
  • 大字段有些特殊,不同数据库处理的方式不一样,大字段的操作常常是以流的方式来处理的。而非一般的字段,一次即可读出数据。
  • Mysql中相关类型:
  • TINYTEXT最大长度为255(2​1​-1)字符的TEXT列。
  • TEXT[(M)]最大长度为65535(2​2​-1)字符的TEXT列。
  • MEDIUMTEXT最大长度为16777215(2​3​-1)字符的TEXT列。
  • LONGTEXT最大长度为4294967295或4GB(2​4​-1)字符的TEXT列。
  • 测试CLOB文本大对象的使用
  • 包含:将字符串、文件内容插入数据库中的CLOB字段,将CLOB字段值取出来操作
    package com.sxt.jdbc; import java.io.*; import java.sql.*; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 测试CLOB文本大对象的使用 * 包含:将字符串、文件内容插入数据库中的CLOB字段,将CLOB字段值取出来操作 */ public class Demo09 { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 加载驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456"); ps = conn.prepareStatement("insert into t_user2 (username, myInfo) values (?, ?)"); ps.setString(1, "狗子"); // 将文本文件的内容直接输入到数据库中 // ps.setClob(2, new FileReader(new File("a1.txt"))); // 通过流的操作写入字符串内容 // ps.setClob(2, new BufferedReader(new InputStreamReader(new ByteArrayInputStream("aaaabbbb".getBytes())))); // ps.executeUpdate(); // 读取Clob字段 ps = conn.prepareStatement("select * from t_user2 where username=?"); ps.setObject(1, "狗子"); rs = ps.executeQuery(); while (rs.next()) { Clob c = rs.getClob("myInfo"); Reader r = c.getCharacterStream(); int temp = 0; while((temp = r.read()) != -1){ System.out.print((char)temp); } System.out.println(); } } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } finally { // 一定要将三个try catch分开写 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }

6.2 BLOB二进制大对象操作

  • Binary Large Object
  • 用于存储大量的二进制数据
  • 大字段有些特殊,不同数据库处理的方式不一样,大字段的操作常常是以流的方式来处理的。而非一般的字段,一次即可独处数据。
  • Mysql中相关类型与CLOB类似,只是将CLOB改为BLOB
  • 测试BLOB二进制大对象的使用
    package com.sxt.jdbc; import java.io.*; import java.sql.*; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 测试BLOB二进制大对象的使用 */ public class Demo10 { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 加载驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456"); // ps = conn.prepareStatement("insert into t_user2 (username, headImg) values (?, ?)"); // ps.setString(1, "狗子2"); // 将图片文件的内容直接输入到数据库中 // ps.setBlob(2, new FileInputStream("test.png")); // ps.executeUpdate(); // 读取Blob字段 ps = conn.prepareStatement("select * from t_user2 where username=?"); ps.setObject(1, "狗子2"); rs = ps.executeQuery(); while (rs.next()) { Blob b = rs.getBlob("headImg"); InputStream is = b.getBinaryStream(); OutputStream os = new FileOutputStream("a1_input.png"); int temp = 0; while((temp = is.read()) != -1){ os.write(temp); } } } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } finally { // 一定要将三个try catch分开写 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }

7. 经典JDBC代码总结

  • JDBC工具类
  • 包括返回数据库驱动连接,关闭各个接口
    package com.sxt.jdbc; import java.io.IOException; import java.sql.*; import java.util.Properties; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: JDBCUTIL.java * @time: 2020/3/8 19:43 * @desc: |JDBC工具类 */ public class JDBCUtil { // 可以帮助我们读取和处理资源文件中的信息 private static Properties pros = null; static { /*静态代码块:只有在加载JDBCUtil类的时候调用一次*/ pros = new Properties(); try { pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/sxt/jdbc/db.properties")); } catch (IOException e) { e.printStackTrace(); } } public static Connection getMysqlConn() { /*获取数据库(mysql)驱动连接*/ // 加载驱动类 try { Class.forName(pros.getProperty("mysqlDriver")); return DriverManager.getConnection( pros.getProperty("mysqlURL"), pros.getProperty("mysqlUser"), pros.getProperty("mysqlPwd")); } catch (Exception e) { e.printStackTrace(); return null; } } public static Connection getOracleConn() { /*获取数据库(oracle)驱动连接*/ // 加载驱动类 try { Class.forName(pros.getProperty("oracleDriver")); return DriverManager.getConnection( pros.getProperty("oracleURL"), pros.getProperty("oracleUser"), pros.getProperty("oraclePwd")); } catch (Exception e) { e.printStackTrace(); return null; } } public static void close(ResultSet rs, Statement ps, Connection conn){ /*关闭接口方法*/ try { if (rs != null){ rs.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (ps != null){ ps.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (conn != null){ conn.close(); } } catch (Exception e) { e.printStackTrace(); } } public static void close(Statement ps, Connection conn){ /*关闭接口方法,重载*/ try { if (ps != null){ ps.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (conn != null){ conn.close(); } } catch (Exception e) { e.printStackTrace(); } } public static void close(Connection conn){ /*关闭接口方法,重载*/ try { if (conn != null){ conn.close(); } } catch (Exception e) { e.printStackTrace(); } } }
  • 配置文件封装
    mysqlURL=jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC #mysqlURL=jdbc:mysql://localhost:3306/sorm?serverTimezone=UTC mysqlDriver=com.mysql.cj.jdbc.Driver mysqlUser=root mysqlPwd=123456 oracleDriver=oracle.jdbc.driver.OracleDriver oracleURL=jdbc:oracle:thin:@localhost:1521:database oracleUser=scott oraclePwd=tiger
  • 测试使用JDBCUtil工具类来简化JDBC开发
    package com.sxt.jdbc; import java.sql.*; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: | 测试使用JDBCUtil工具类来简化JDBC开发 */ public class Demo11 { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JDBCUtil.getMysqlConn(); ps = conn.prepareStatement("insert into t_user (username) values (?)"); ps.setString(1, "hehe"); ps.execute(); } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtil.close(rs, ps, conn); } } }

8. ORM原理

  • ORM的基本思想
  • Object Relationship Mapping:对象关系映射
  • 表结构跟类对应;表中字段和类的属性对应;表中记录和对象对应。
  • 让javabean的属性名和类型尽量和数据库保持一致!
  • 一条记录对应一个对象。将这些查询到的对象放到容器中(List,Set,Map)
  • 将表中的一条记录封装到Object数组中
  • 将表中的一条记录封装到map中
  • 将表中的一条记录封装到javabean对象中
  • 测试使用Object数组来封装一条记录,使用List<Object[]>存储多条记录
    package com.sxt.testORM; import com.sxt.jdbc.JDBCUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Demo01.java * @time: 2020/3/10 13:29 * @desc: |测试使用Object数组来封装一条记录 * 使用List<Object[]>存储多条记录 */ public class Demo01 { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; List<Object[]> list = new ArrayList<>(); try { conn = JDBCUtil.getMysqlConn(); ps = conn.prepareStatement("select empname, salary, age from emp where id > ?"); ps.setObject(1, 0); rs = ps.executeQuery(); while (rs.next()) { // System.out.println(rs.getString(1) + "-->" + rs.getDouble(2) + "-->" + rs.getInt(3)); Object[] objs = new Object[3]; objs[0] = rs.getObject(1); objs[1] = rs.getObject(2); objs[2] = rs.getObject(3); list.add(objs); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtil.close(rs, ps, conn); } for (Object[] objs : list) { System.out.println(objs[0] + "-->" + objs[1] + "-->" + objs[2]); } } }
  • 测试使用Map来封装一条记录,使用List<Map>存储多条记录(也可用Map<Map>
    package com.sxt.testORM; import com.sxt.jdbc.JDBCUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: |测试使用Map来封装一条记录 * 使用List<Map>存储多条记录(也可用Map<Map>) */ public class Demo02 { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; // 使用一个Map封装一条记录 List<Map<String, Object>> list = new ArrayList<>(); try { conn = JDBCUtil.getMysqlConn(); ps = conn.prepareStatement("select empname, salary, age from emp where id > ?"); ps.setObject(1, 0); rs = ps.executeQuery(); while (rs.next()) { // System.out.println(rs.getString(1) + "-->" + rs.getDouble(2) + "-->" + rs.getInt(3)); Map<String, Object> row = new HashMap<>(); row.put("empname", rs.getString(1)); row.put("salary", rs.getString(2)); row.put("age", rs.getString(3)); list.add(row); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtil.close(rs, ps, conn); } // 遍历List和Map for (Map<String, Object> row : list) { for (String key : row.keySet()) { System.out.print(key + "-->" + row.get(key) + "\t\t"); } System.out.println(); } } }
  • 使用Javabean对象来封装一条记录,使用List<Javabean>存储多条记录
    package com.sxt.testORM; import com.sxt.jdbc.JDBCUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @desc: |使用Javabean对象来封装一条记录 * 使用List<Javabean>存储多条记录 */ public class Demo03 { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; List<Emp> list = new ArrayList<>(); try { conn = JDBCUtil.getMysqlConn(); ps = conn.prepareStatement("select empname, salary, age from emp where id > ?"); ps.setObject(1, 0); rs = ps.executeQuery(); while (rs.next()) { // System.out.println(rs.getString(1) + "-->" + rs.getDouble(2) + "-->" + rs.getInt(3)); Emp emp = new Emp(rs.getString(1), rs.getInt(2), rs.getDouble(3)); list.add(emp); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtil.close(rs, ps, conn); } for (Emp e: list) { System.out.println(e); } } }
  • 其中需要为每一个表定义相同结构的类
  • Emp
    package com.sxt.testORM; import java.sql.Date; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Emp.java * @time: 2020/3/10 14:35 * @desc: |表结构和类对应 */ public class Emp { private Integer id; private String empname; private Integer age; private Double salary; private Date birthday; private Integer deptId; public Emp(String empname, Integer age, Double salary) { this.empname = empname; this.age = age; this.salary = salary; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getEmpname() { return empname; } public void setEmpname(String empname) { this.empname = empname; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public Integer getDeptId() { return deptId; } public void setDeptId(Integer deptId) { this.deptId = deptId; } public Emp(String empname, Integer age, Double salary, Date birthday, Integer deptId) { this.empname = empname; this.age = age; this.salary = salary; this.birthday = birthday; this.deptId = deptId; } public Emp(Integer id, String empname, Integer age, Double salary, Date birthday, Integer deptId) { this.id = id; this.empname = empname; this.age = age; this.salary = salary; this.birthday = birthday; this.deptId = deptId; } public Emp() { } @Override public String toString() { return empname + "-->" + age + "-->" + salary; } }
  • Dept
    package com.sxt.testORM; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Dept.java * @time: 2020/3/10 14:38 * @desc: | */ public class Dept { private Integer id; private String dname; private String address; public Dept() { } public Dept(String dname, String address) { this.dname = dname; this.address = address; } public Dept(Integer id, String dname, String address) { this.id = id; this.dname = dname; this.address = address; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }

9. 手写SORM框架

  • Simple Object Relationship Mapping
  • 我们希望设计一个可以实现对象和SQL自动映射的框架,但是整体用法和设计比Hibernate简单。砍掉不必要的功能。
  • 会穿插使用设计模式
  • 从对象到sql
  • 增加:将对象对应成sql语句,执行sql,插入数据库中
  • 删除:根据对象主键的值,生成sql,执行,从库中删除
  • 修改:根据对象需要修改属性的值,生成sql,执行
  • 从sql到对象
  • 查询:根据结果分类
  • 多行多列:​​List<Javabean>​
  • 一行多列:Javabean
  • 一行一列:普通对象:Object,数字:Number
  • 核心架构:
  • Query接口:负责查询(对外提供服务的核心类)
  • QueryFactory类:负责根据配置信息创建query对象
  • TypeConvertor类:负责类型转换
  • TableContext类:负责获取管理数据库所有表结构和类结构的关系,并可以根据表结构生成类结结构。
  • DBManager类:根据配置信息,维持连接对象的管理(增加连接池功能)
  • 工具类:
  • JDBCUtils封装常用JDBC操作
  • StringUtils封装常用字符串操作
  • JavaFileUtils封装Java文件操作
  • ReflectUtils封装常用反射操作
  • 架构图
    尚学堂JAVA高级学习笔记_2/2_java_13
  • 核心bean,封装相关数据
  • ColumnInfo:封装表中一个字段的信息(字段类型、字段名、键类型)
  • Configuration:封装配置文件信息
  • TableInfo:封装一张表的信息
  • 针对SORM框架的说明:
  • 核心思想:使用简单、性能高、极易上手!
  • 配置文件:目前使用资源文件、后期项目复杂后可以增加XML文件配置和注解
  • 类名由表名生成,只有首字母大写有区别,其他无区别
  • Java对象的属性由表中字段生成,完全对应
  • 目前,只支持表中只有一个主键,联合主键不支持

9.1 第一版SORM

  • bean
  • ColumnInfo
    package com.sxt.SORM.bean; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: ColumnInfo.java * @time: 2020/3/11 13:46 * @desc: |封装表中一个字段的信息 */ public class ColumnInfo { // 字段名称 private String name; // 字段数据类型 private String dataType; // 字段的键类型(0普通键;1主键;2外键) private int keyType; public ColumnInfo() { } public ColumnInfo(String name, String dataType, int keyType) { this.name = name; this.dataType = dataType; this.keyType = keyType; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDataType() { return dataType; } public void setDataType(String dataType) { this.dataType = dataType; } public int getKeyType() { return keyType; } public void setKeyType(int keyType) { this.keyType = keyType; } }
  • Configuration
    package com.sxt.SORM.bean; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Configuration.java * @time: 2020/3/11 13:55 * @desc: |管理配置信息 */ public class Configuration { // 正在使用哪个数据库 private String usingDb; // jdbc的url private String URL; // 驱动类 private String driver; // 数据库的用户名 private String user; // 数据库的密码 private String pwd; // 项目的源码路径 private String srcPath; // 扫描生成java类的包(po的意思是Persistence Object持久化对象) private String poPackage; public Configuration() { } public Configuration(String usingDb, String URL, String driver, String user, String pwd, String srcPath, String poPackage) { this.usingDb = usingDb; this.URL = URL; this.driver = driver; this.user = user; this.pwd = pwd; this.srcPath = srcPath; this.poPackage = poPackage; } public String getUsingDb() { return usingDb; } public void setUsingDb(String usingDb) { this.usingDb = usingDb; } public String getURL() { return URL; } public void setURL(String URL) { this.URL = URL; } public String getDriver() { return driver; } public void setDriver(String driver) { this.driver = driver; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public String getSrcPath() { return srcPath; } public void setSrcPath(String srcPath) { this.srcPath = srcPath; } public String getPoPackage() { return poPackage; } public void setPoPackage(String poPackage) { this.poPackage = poPackage; } }
  • JavaFieldGetSet
    package com.sxt.SORM.bean; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: JavaFieldGetSet.java * @time: 2020/3/11 18:24 * @desc: |封装了java属性和get、set方法的源代码 */ public class JavaFieldGetSet { // 属性源码信息。如:private int userId; private String fieldInfo; // get方法的源码信息。如:public int getUserId; private String getInfo; // set方法的源码信息。如:public void setUserId(int id){this.id = id;} private String setInfo; public JavaFieldGetSet() { } public JavaFieldGetSet(String fieldInfo, String getInfo, String setInfo) { this.fieldInfo = fieldInfo; this.getInfo = getInfo; this.setInfo = setInfo; } public String getFieldInfo() { return fieldInfo; } public void setFieldInfo(String fieldInfo) { this.fieldInfo = fieldInfo; } public String getGetInfo() { return getInfo; } public void setGetInfo(String getInfo) { this.getInfo = getInfo; } public String getSetInfo() { return setInfo; } public void setSetInfo(String setInfo) { this.setInfo = setInfo; } @Override public String toString() { // System.out.println(fieldInfo); // System.out.println(getInfo); // System.out.println(setInfo); return super.toString(); } }
  • TableInfo
    package com.sxt.SORM.bean; import java.util.List; import java.util.Map; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: TableInfo.java * @time: 2020/3/11 13:56 * @desc: |存储表结构信息 */ public class TableInfo { // 表名 private String tname; // 所有字段的信息 private Map<String, ColumnInfo> columns; // 唯一主键(目前只能处理表中有且只有一个的情况) private ColumnInfo onlyPriKey; // 如果联合主键,则在这里存储 private List<ColumnInfo> priKeys; public TableInfo() { } public TableInfo(String tname, List<ColumnInfo> priKeys, Map<String, ColumnInfo> columns) { this.tname = tname; this.columns = columns; this.priKeys = priKeys; } public String getTname() { return tname; } public void setTname(String tname) { this.tname = tname; } public Map<String, ColumnInfo> getColumns() { return columns; } public void setColumns(Map<String, ColumnInfo> columns) { this.columns = columns; } public ColumnInfo getOnlyPriKey() { return onlyPriKey; } public void setOnlyPriKey(ColumnInfo onlyPriKey) { this.onlyPriKey = onlyPriKey; } public List<ColumnInfo> getPriKeys() { return priKeys; } public void setPriKeys(List<ColumnInfo> priKeys) { this.priKeys = priKeys; } }
  • core
  • Query
    package com.sxt.SORM.core; import java.util.List; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Query.java * @time: 2020/3/10 17:31 * @desc: |负责查询(对外提供服务的核心类) */ public interface Query { /** * 直接执行一个DML语句 * * @param sql sql语句 * @param params 参数 * @return 执行sql语句后影响记录的行数 */ public int executeDML(String sql, Object[] params); /** * 将一个对象存储到数据库中 * * @param obj 要存储的对象 */ public void insert(Object obj); /** * 删除clazz表示类对应的表中的记录(指定主键id的记录) * 把对象中不为null的属性往数据库中存储!如果数字为null则放0 * @param clazz 跟表对应的类的Class对象 * @param id 主键的值 */ // delete from User where id = 2; public void delete(Class clazz, Object id); /** * 删除对象在数据库中对应的记录(对象所在类对应到表,对象的主键对应到的记录) * * @param obj */ public void delete(Object obj); /** * 更新对象对应的记录,并且只更新指定的字段的值 * * @param obj 索要更新的对象 * @param fieldNames 更新的属性列表 * @return 执行sql语句后影响记录的行数 */ // update user set uname=?, pwe=? public int update(Object obj, String[] fieldNames); /** * 查询返回多行记录,并将每行记录封装到clazz指定的类的对象中 * * @param sql 查询语句 * @param clazz 封装数据的javabean类的Class对象 * @param params sql的参数 * @return 返回查询到的结果 */ public List queryRows(String sql, Class clazz, Object[] params); /** * 查询返回一行记录,并将该记录封装到clazz指定的类的对象中 * * @param sql 查询语句 * @param clazz 封装数据的javabean类的Class对象 * @param params sql的参数 * @return 返回查询到的结果 */ public Object queryUniqueRows(String sql, Class clazz, Object[] params); /** * 查询返回一个值(一行一列),并将该值返回 * * @param sql 查询语句 * @param params sql的参数 * @return 返回查询到的结果 */ public Object queryValue(String sql, Object[] params); /** * 查询返回一个数字(一行一列),并将该值返回 * * @param sql 查询语句 * @param params sql的参数 * @return 返回查询到的数字 */ public Number queryNumber(String sql, Object[] params); }
  • MysqlQuery
    package com.sxt.SORM.core; import com.sxt.SORM.bean.ColumnInfo; import com.sxt.SORM.bean.TableInfo; import com.sxt.SORM.po.Emp; import com.sxt.SORM.utils.JDBCUtils; import com.sxt.SORM.utils.ReflectUtils; import com.sxt.SORM.vo.EmpVO; import java.lang.reflect.Field; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: MysqlQuery.java * @time: 2020/3/13 16:54 * @desc: |负责针对mysql数据库的查询 */ public class MysqlQuery implements Query { public static void main(String[] args) { Object obj = new MysqlQuery().queryValue("select count(*) from emp where salary>?", new Object[]{1000}); System.out.println(obj); } /** * 复杂多行查询测试 */ public static void testQueryRows() { List<Emp> list = new MysqlQuery().queryRows("select id,empname,age from emp where age>? and salary<?", Emp.class, new Object[]{1, 9000}); for (Emp e : list) { System.out.println(e.getEmpname()); } String sql2 = "select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e" + " " + "join dept d on e.deptId=d.id;"; List<EmpVO> list2 = new MysqlQuery().queryRows(sql2, EmpVO.class, null); for (EmpVO e : list2) { System.out.println(e.getEmpname() + "-" + e.getDeptAddr() + "-" + e.getXinshui()); } } /** * 增删改操作测试 */ public static void testDML() { Emp e = new Emp(); e.setEmpname("Tom"); e.setBirthday(new java.sql.Date(System.currentTimeMillis())); e.setAge(30); e.setSalary(8888.0); e.setId(1); // new MysqlQuery().delete(e); // new MysqlQuery().insert(e); new MysqlQuery().update(e, new String[]{"empname", "age", "salary"}); } @Override public int executeDML(String sql, Object[] params) { Connection conn = DBManager.getConn(); int count = 0; PreparedStatement ps = null; try { ps = conn.prepareStatement(sql); // 给sql设置参数,就是?位置的参数 JDBCUtils.handleParams(ps, params); count = ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { DBManager.close(ps, conn); } return count; } @Override public void insert(Object obj) { // obj --> 表中。 insert into 表名(id, name, pwd) values (?, ?, ?) Class c = obj.getClass(); // 存储sql的参数对象 List<Object> params = new ArrayList<>(); TableInfo tableInfo = TableContext.poClassTableMap.get(c); StringBuilder sql = new StringBuilder("insert into " + tableInfo.getTname() + " ("); // 计算不为空的属性值 int countNotNullField = 0; // 目前只能处理数据库来维护自增的方式 Field[] fs = c.getDeclaredFields(); for (Field f : fs) { String fieldName = f.getName(); Object fieldValue = ReflectUtils.invokeGet(fieldName, obj); if (fieldValue != null) { // 如果该属性值不为空 countNotNullField++; sql.append(fieldName + ","); params.add(fieldValue); } } // 把最后一个属性后面的,换成) sql.setCharAt(sql.length() - 1, ')'); sql.append(" values ("); for (int i = 0; i < countNotNullField; i++) { sql.append("?,"); } sql.setCharAt(sql.length() - 1, ')'); executeDML(sql.toString(), params.toArray()); } @Override public void delete(Class clazz, Object id) { // Emp.class, 2 --> delete from emp where id=2 // 通过Class对象找TableInfo TableInfo tableInfo = TableContext.poClassTableMap.get(clazz); // 获得主键 ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey(); String sql = "delete from " + tableInfo.getTname() + " where " + onlyPriKey.getName() + "=?;"; executeDML(sql, new Object[]{id}); } @Override public void delete(Object obj) { Class c = obj.getClass(); TableInfo tableInfo = TableContext.poClassTableMap.get(c); // 获得主键 ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey(); // 通过反射机制,调用属性对应的get方法或set方法 Object priKeyValue = ReflectUtils.invokeGet(onlyPriKey.getName(), obj); delete(obj.getClass(), priKeyValue); } @Override public int update(Object obj, String[] fieldNames) { // obj{"uname", "pwd} --> update 表名 set uname=?, pwd=? where id=? Class c = obj.getClass(); List<Object> params = new ArrayList<>(); TableInfo tableInfo = TableContext.poClassTableMap.get(c); ColumnInfo priKey = tableInfo.getOnlyPriKey(); StringBuilder sql = new StringBuilder("update " + tableInfo.getTname() + " set "); for (String fname : fieldNames) { Object fvalue = ReflectUtils.invokeGet(fname, obj); params.add(fvalue); sql.append(fname + "=?,"); } sql.setCharAt(sql.length() - 1, ' '); sql.append(" where "); sql.append(priKey.getName() + "=?"); params.add(ReflectUtils.invokeGet(priKey.getName(), obj)); return executeDML(sql.toString(), params.toArray()); } @Override public List queryRows(String sql, Class clazz, Object[] params) { Connection conn = DBManager.getConn(); // 存放查询结果的容器 List list = null; PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(sql); // 给sql设置参数,就是?位置的参数 JDBCUtils.handleParams(ps, params); rs = ps.executeQuery(); ResultSetMetaData metaData = rs.getMetaData(); // 多行 while (rs.next()) { if (list == null) { list = new ArrayList(); } // 调用javabean的无参构造器 Object rowObj = clazz.newInstance(); // 多列 select username, pwd, age from user where id>? and age>? for (int i = 0; i < metaData.getColumnCount(); i++) { // username String columnName = metaData.getColumnLabel(i + 1); Object columnValue = rs.getObject(i + 1); // 调用rowObj对象的setUsername(String uname)方法,将columnValue的值设置进去 ReflectUtils.invokeSet(rowObj, columnName, columnValue); } list.add(rowObj); } } catch (Exception e) { e.printStackTrace(); } finally { DBManager.close(ps, conn); } return list; } @Override public Object queryUniqueRows(String sql, Class clazz, Object[] params) { List list = queryRows(sql, clazz, params); return (list == null && list.size() > 0) ? null : list.get(0); } @Override public Object queryValue(String sql, Object[] params) { Connection conn = DBManager.getConn(); // 存储查询结果的对象 Object value = null; PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(sql); // 给sql设置参数,就是?位置的参数 JDBCUtils.handleParams(ps, params); rs = ps.executeQuery(); // 多行 while (rs.next()) { // select count(*) from user value = rs.getObject(1); } } catch (Exception e) { e.printStackTrace(); } finally { DBManager.close(ps, conn); } return value; } @Override public Number queryNumber(String sql, Object[] params) { return (Number) queryValue(sql, params); } }
  • TypeConvertor
    package com.sxt.SORM.core; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: TypeConvertor.java * @time: 2020/3/11 13:39 * @desc: |负责java数据类型和数据库数据类型的互相转换 */ public interface TypeConvertor { /** * 将数据库数据类型转化成java的数据类型 * @param columnType 数据库字段的数据类型 * @return java的数据类型 */ public String databaseType2JavaType(String columnType); /** * 将java数据类型转化为数据库数据类型 * @param javaDataType java数据类型 * @return 数据库数据类型 */ public String javaType2DatabaseType(String javaDataType); }
  • MySqlTypeConvertor
    package com.sxt.SORM.core; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: MySqlTypeConvertor.java * @time: 2020/3/11 18:16 * @desc: |mysql数据类型和java数据类型的转换 */ public class MySqlTypeConvertor implements TypeConvertor { @Override public String databaseType2JavaType(String columnType) { // varchar --> String if ("varchar".equalsIgnoreCase(columnType) || "char".equalsIgnoreCase(columnType)) { return "String"; } else if ("int".equalsIgnoreCase(columnType) || "tinyint".equalsIgnoreCase(columnType) || "smallint".equalsIgnoreCase(columnType) || "integer".equalsIgnoreCase(columnType)) { return "Integer"; } else if ("bigint".equalsIgnoreCase(columnType)) { return "long"; } else if ("double".equalsIgnoreCase(columnType) || "float".equalsIgnoreCase(columnType)) { return "Double"; } else if ("clob".equalsIgnoreCase(columnType)) { return "java.sql.Clob"; } else if ("blob".equalsIgnoreCase(columnType)) { return "java.sql.Blob"; }else if("date".equalsIgnoreCase(columnType)){ return "java.sql.Date"; }else if("time".equalsIgnoreCase(columnType)){ return "java.sql.Time"; }else if("timestamp".equalsIgnoreCase(columnType)){ return "java.sql.Timestamp"; } return null; } @Override public String javaType2DatabaseType(String javaDataType) { return null; } }
  • DBManager
    package com.sxt.SORM.core; import com.sxt.SORM.bean.Configuration; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: DBManager.java * @time: 2020/3/11 13:43 * @desc: |根据配置信息,维持连接对象的管理(增加连接池功能) */ public class DBManager { private static Configuration conf; static { // 静态代码块 Properties pros = new Properties(); try { pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/sxt/SORM/db.properties")); } catch (IOException e) { e.printStackTrace(); } conf = new Configuration(); conf.setDriver(pros.getProperty("driver")); conf.setPoPackage(pros.getProperty("poPackage")); conf.setPwd(pros.getProperty("pwd")); conf.setSrcPath(pros.getProperty("srcPath")); conf.setURL(pros.getProperty("URL")); conf.setUser(pros.getProperty("user")); conf.setUsingDb(pros.getProperty("usingDB")); } public static Connection getConn() { /*获取数据库(mysql)驱动连接*/ // 加载驱动类 try { Class.forName(conf.getDriver()); // 直接建立连接,后期增加连接池处理,提高效率! return DriverManager.getConnection(conf.getURL(), conf.getUser(), conf.getPwd()); } catch (Exception e) { e.printStackTrace(); return null; } } public static void close(ResultSet rs, Statement ps, Connection conn) { /*关闭接口方法*/ try { if (rs != null) { rs.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (ps != null) { ps.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (conn != null) { conn.close(); } } catch (Exception e) { e.printStackTrace(); } } public static void close(Statement ps, Connection conn) { /*关闭接口方法,重载*/ try { if (ps != null) { ps.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (conn != null) { conn.close(); } } catch (Exception e) { e.printStackTrace(); } } public static void close(Connection conn) { /*关闭接口方法,重载*/ try { if (conn != null) { conn.close(); } } catch (Exception e) { e.printStackTrace(); } } public static Configuration getConf(){ return conf; } }
  • TableContext
    package com.sxt.SORM.core; import com.sxt.SORM.bean.ColumnInfo; import com.sxt.SORM.bean.TableInfo; import com.sxt.SORM.utils.JavaFileUtils; import com.sxt.SORM.utils.StringUtils; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: TableContext.java * @time: 2020/3/11 13:42 * @desc: |负责获取管理数据库所有表结构和类结构的关系,并可以根据表结构生成类结结构 */ public class TableContext { // 表名为key,表信息对象为value public static Map<String, TableInfo> tables = new HashMap<>(); // 将po的calss对象和表信息对象关联起来,便于重用。 public static Map<Class, TableInfo> poClassTableMap = new HashMap<>(); private TableContext() { } static { try { // 初始化获得的表信息 Connection conn = DBManager.getConn(); DatabaseMetaData dbmd = conn.getMetaData(); ResultSet tableSet = dbmd.getTables("", "%", "%", new String[]{"TABLE"}); while (tableSet.next()) { // 循环每个表名 String tableName = (String) tableSet.getObject("TABLE_NAME"); TableInfo ti = new TableInfo(tableName, new ArrayList<ColumnInfo>(), new HashMap<String, ColumnInfo>()); tables.put(tableName, ti); // 查询表中的所有字段 ResultSet set = dbmd.getColumns("", "%", tableName, "%"); while (set.next()) { // 循环每个列名 ColumnInfo ci = new ColumnInfo(set.getString("COLUMN_NAME"), set.getString("TYPE_NAME"), 0); ti.getColumns().put(set.getString("COLUMN_NAME"), ci); } // 查询表中的主键 // System.out.println(tableName); ResultSet set2 = dbmd.getPrimaryKeys("", "%", tableName); while (set2.next()) { ColumnInfo ci2 = (ColumnInfo) ti.getColumns().get(set2.getObject("COLUMN_NAME")); // 设置为主键类型 ci2.setKeyType(1); ti.getPriKeys().add(ci2); } if (ti.getPriKeys().size() > 0) { // 取唯一主键。方便使用。如果是联合主键。则为空! ti.setOnlyPriKey(ti.getPriKeys().get(0)); } } } catch (Exception e) { e.printStackTrace(); } // 更新类结构 updateJavaPOFile(); // 加载po包下面的所有类,便于重用,提高效率! loadPOTables(); } /** * 根据表结构,更新配置的po包下面的java类 */ public static void updateJavaPOFile() { Map<String, TableInfo> map = TableContext.tables; for (TableInfo t : map.values()) { JavaFileUtils.createJavaPOFile(t, new MySqlTypeConvertor()); } } /** * 加载po包下面的类 */ public static void loadPOTables() { for (TableInfo tableInfo : tables.values()) { try { Class c = Class.forName(DBManager.getConf().getPoPackage() + "." + StringUtils.firstChar2UpperCase(tableInfo.getTname())); poClassTableMap.put(c, tableInfo); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } public static void main(String[] args) { Map<String, TableInfo> tables = TableContext.tables; System.out.println(tables); } }
  • utils
  • JavaFileUtils
    package com.sxt.SORM.utils; import com.sxt.SORM.bean.ColumnInfo; import com.sxt.SORM.bean.JavaFieldGetSet; import com.sxt.SORM.bean.TableInfo; import com.sxt.SORM.core.DBManager; import com.sxt.SORM.core.MySqlTypeConvertor; import com.sxt.SORM.core.TableContext; import com.sxt.SORM.core.TypeConvertor; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: JavaFileUtils.java * @time: 2020/3/11 13:44 * @desc: | 封装了生成java文件(源代码)常用操作 */ public class JavaFileUtils { /** * 根据字段信息生成java属性信息。如varchar username --> private String username;以及相应的set和get方法源码 * * @param column 字段信息 * @param convertor 类型转化器 * @return java属性和set/get方法源码 */ public static JavaFieldGetSet createFieldGetSetSRC(ColumnInfo column, TypeConvertor convertor) { JavaFieldGetSet jfgs = new JavaFieldGetSet(); String javaFieldType = convertor.databaseType2JavaType(column.getDataType()); jfgs.setFieldInfo("\tprivate " + javaFieldType + " " + column.getName() + ";\n"); // public String getUsername(){return username;} StringBuilder getSrc = new StringBuilder(); getSrc.append("\tpublic " + javaFieldType + " get" + StringUtils.firstChar2UpperCase(column.getName()) + "(){\n"); getSrc.append("\t\treturn " + column.getName() + ";\n"); getSrc.append("\t}\n"); jfgs.setGetInfo(getSrc.toString()); // public void setUsername(String username){this.username = username;} StringBuilder setSrc = new StringBuilder(); setSrc.append("\tpublic void set" + StringUtils.firstChar2UpperCase(column.getName()) + "("); setSrc.append(javaFieldType + " " + column.getName() + "){\n"); setSrc.append("\t\tthis." + column.getName() + " = " + column.getName() + ";\n"); setSrc.append("\t}\n"); jfgs.setSetInfo(setSrc.toString()); return jfgs; } /** * 根据表信息生成java类的源代码 * * @param tableInfo 表信息 * @param convertor 数据类型转化器 * @return java类的源代码 */ public static String createJavaSrc(TableInfo tableInfo, TypeConvertor convertor) { Map<String, ColumnInfo> columns = tableInfo.getColumns(); List<JavaFieldGetSet> javaFields = new ArrayList<>(); for (ColumnInfo c : columns.values()) { javaFields.add(createFieldGetSetSRC(c, convertor)); } StringBuilder src = new StringBuilder(); // 生成package语句 src.append("package " + DBManager.getConf().getPoPackage() + ";\n\n"); // 生成import语句 src.append("import java.sql.*;\n"); src.append("import java.util.*;\n\n"); // 生成类声明语句 src.append("public class " + StringUtils.firstChar2UpperCase(tableInfo.getTname()) + " {\n\n"); // 生成属性列表 for (JavaFieldGetSet f : javaFields) { src.append(f.getFieldInfo()); } src.append("\n\n"); // 生成set方法列表 for (JavaFieldGetSet f : javaFields) { src.append(f.getSetInfo()); } // 生成get方法列表 for (JavaFieldGetSet f : javaFields) { src.append(f.getGetInfo()); } // 生成类结束 src.append("}\n"); // System.out.println(src); return src.toString(); } public static void createJavaPOFile(TableInfo tableInfo, TypeConvertor convertor) { String src = createJavaSrc(tableInfo, convertor); String srcPath = DBManager.getConf().getSrcPath() + "\\"; String packagePath = DBManager.getConf().getPoPackage().replaceAll("\\.", "/"); // 修正poPackage路径,因为没有重新创建项目 String[] packagePath_list = packagePath.split("/"); packagePath = packagePath_list[packagePath_list.length - 1]; File f = new File(srcPath + packagePath); // System.out.println(f.getAbsolutePath()); if (!f.exists()) { // 指定目录不存在则帮助用户建立该目录 f.mkdirs(); } BufferedWriter bw = null; try { bw = new BufferedWriter(new FileWriter(f.getAbsolutePath() + "/" + StringUtils.firstChar2UpperCase(tableInfo.getTname()) + ".java")); bw.write(src); System.out.println("建立表" + tableInfo.getTname() + "对应的java类"); bw.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (bw != null) { bw.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { // 测试每一个表的field,set、get方法源码生成 // ColumnInfo ci = new ColumnInfo("username", "int", 0); // JavaFieldGetSet f = createFieldGetSetSRC(ci, new MySqlTypeConvertor()); // System.out.println(f); // System.out.println("\n--------------------" + "分割线" + "--------------------\n"); // 测试每一个表的从头到尾完全源码生成 // Map<String, TableInfo> map = TableContext.tables; // TableInfo t = map.get("emp"); // createJavaSrc(t, new MySqlTypeConvertor()); Map<String, TableInfo> map = TableContext.tables; // TableInfo t = map.get("emp"); for(TableInfo t: map.values()) { createJavaPOFile(t, new MySqlTypeConvertor()); } } }
  • JDBCUtils
    package com.sxt.SORM.utils; import java.sql.PreparedStatement; import java.sql.SQLException; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: JDBCUtils.java * @time: 2020/3/11 13:43 * @desc: | 封装了JDBC查询常用的操作 */ public class JDBCUtils { /** * 给sql设置参数,就是?位置的参数 * @param ps 预编译sql语句对象 * @param params 参数 */ public static void handleParams(PreparedStatement ps, Object[] params) { if (params != null) { for (int i = 0; i < params.length; i++) { try { ps.setObject(1 + i, params[i]); } catch (SQLException e) { e.printStackTrace(); } } } } }
  • StringUtils
    package com.sxt.SORM.utils; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: StringUtils.java * @time: 2020/3/11 13:44 * @desc: | 封装了字符串常用的操作 */ public class StringUtils { /** * 将目标字符串首字母变为大写 * @param str 目标字符串 * @return 首字母变为大写的字符串 */ public static String firstChar2UpperCase(String str){ // abcd-->Abcd return str.toUpperCase().substring(0, 1) + str.substring(1); } }
  • ReflectUtils
    package com.sxt.SORM.utils; import java.lang.reflect.Method; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: ReflectUtils.java * @time: 2020/3/11 13:44 * @desc: | 封装了反射的常用操作 */ public class ReflectUtils { /** * 调用obj对象对应属性fieldName的get方法 * * @param fieldName 属性名 * @param obj Object对象 * @return */ public static Object invokeGet(String fieldName, Object obj) { // 通过反射机制,调用属性对应的get方法或set方法 try { Class c = obj.getClass(); Method m = c.getDeclaredMethod("get" + StringUtils.firstChar2UpperCase(fieldName), null); return m.invoke(obj, null); } catch (Exception e) { e.printStackTrace(); return null; } } public static void invokeSet(Object obj, String columnName, Object columnValue) { try { if (columnValue != null) { Method m = obj.getClass().getDeclaredMethod("set" + StringUtils.firstChar2UpperCase(columnName), columnValue.getClass()); m.invoke(obj, columnValue); } } catch (Exception e) { e.printStackTrace(); } } }
  • po(自动生成的代码保存位置)
  • Dept
    package com.sxt.SORM.po; public class Dept { private String address; private Integer id; private String dname; public void setAddress(String address){ this.address = address; } public void setId(Integer id){ this.id = id; } public void setDname(String dname){ this.dname = dname; } public String getAddress(){ return address; } public Integer getId(){ return id; } public String getDname(){ return dname; } }
  • Emp
    package com.sxt.SORM.po; public class Emp { private String empname; private java.sql.Date birthday; private Double bonus; private Integer deptId; private Integer id; private Double salary; private Integer age; public String getEmpname() { return empname; } public void setEmpname(String empname) { this.empname = empname; } public java.sql.Date getBirthday() { return birthday; } public void setBirthday(java.sql.Date birthday) { this.birthday = birthday; } public Double getBonus() { return bonus; } public void setBonus(Double bonus) { this.bonus = bonus; } public Integer getDeptId() { return deptId; } public void setDeptId(Integer deptId) { this.deptId = deptId; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
  • vo(复杂查询生成的类所保存的位置)
  • EmpVO
    package com.sxt.SORM.vo; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: EmpVO.java * @time: 2020/3/15 12:44 * @desc: | */ public class EmpVO { // select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e // join dept d on e.deptId=d.id; private Integer id; private String empname; private Double xinshui; private Integer age; private String deptName; private String deptAddr; public EmpVO() { } public EmpVO(Integer id, String empname, Double xinshui, Integer age, String deptName, String deptAddr) { this.id = id; this.empname = empname; this.xinshui = xinshui; this.age = age; this.deptName = deptName; this.deptAddr = deptAddr; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getEmpname() { return empname; } public void setEmpname(String empname) { this.empname = empname; } public Double getXinshui() { return xinshui; } public void setXinshui(Double xinshui) { this.xinshui = xinshui; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } public String getDeptAddr() { return deptAddr; } public void setDeptAddr(String deptAddr) { this.deptAddr = deptAddr; } }
  • 配置文件(db.properties)
    usingDB=mysql URL=jdbc:mysql://localhost:3306/sorm?serverTimezone=UTC driver=com.mysql.cj.jdbc.Driver user=root pwd=123456 srcPath=F:/BookStudy/else/JAVAPro/src/com/sxt/SORM poPackage=com.sxt.SORM.po
  • README(说明文件)
    1. 在src下建立db.properties 2. 每张表只有一个主键,不能处理多个主键的情况 3. po尽量使用包装类,不要使用基本数据类型 4. 目前只能处理数据库来维护自增的方式

9.2 通过模板方法模式对Query进行优化

  • Query
    package com.sxt.SORM.core; import com.sxt.SORM.bean.ColumnInfo; import com.sxt.SORM.bean.TableInfo; import com.sxt.SORM.utils.JDBCUtils; import com.sxt.SORM.utils.ReflectUtils; import java.lang.reflect.Field; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Query.java * @time: 2020/3/10 17:31 * @desc: |负责查询(对外提供服务的核心类) */ public abstract class Query implements Cloneable { /** * 采用模板方法模式将JDBC操作封装成模板,变于重用 * * @param sql sql语句 * @param params sql的参数 * @param clazz 记录要封装到的java类 * @param back CallBack的实现类,实现回调 * @return 返回查询结果 */ public Object executeQueryTemplate(String sql, Object[] params, Class clazz, CallBack back) { Connection conn = DBManager.getConn(); // 存放查询结果的容器 PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(sql); // 给sql设置参数,就是?位置的参数 JDBCUtils.handleParams(ps, params); rs = ps.executeQuery(); ResultSetMetaData metaData = rs.getMetaData(); return back.doExecute(conn, ps, rs); } catch (Exception e) { e.printStackTrace(); return null; } finally { DBManager.close(ps, conn); } } /** * 直接执行一个DML语句 * * @param sql sql语句 * @param params 参数 * @return 执行sql语句后影响记录的行数 */ public int executeDML(String sql, Object[] params) { Connection conn = DBManager.getConn(); int count = 0; PreparedStatement ps = null; try { ps = conn.prepareStatement(sql); // 给sql设置参数,就是?位置的参数 JDBCUtils.handleParams(ps, params); count = ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { DBManager.close(ps, conn); } return count; } /** * 将一个对象存储到数据库中 * * @param obj 要存储的对象 */ public void insert(Object obj) { // obj --> 表中。 insert into 表名(id, name, pwd) values (?, ?, ?) Class c = obj.getClass(); // 存储sql的参数对象 List<Object> params = new ArrayList<>(); TableInfo tableInfo = TableContext.poClassTableMap.get(c); StringBuilder sql = new StringBuilder("insert into " + tableInfo.getTname() + " ("); // 计算不为空的属性值 int countNotNullField = 0; // 目前只能处理数据库来维护自增的方式 Field[] fs = c.getDeclaredFields(); for (Field f : fs) { String fieldName = f.getName(); Object fieldValue = ReflectUtils.invokeGet(fieldName, obj); if (fieldValue != null) { // 如果该属性值不为空 countNotNullField++; sql.append(fieldName + ","); params.add(fieldValue); } } // 把最后一个属性后面的,换成) sql.setCharAt(sql.length() - 1, ')'); sql.append(" values ("); for (int i = 0; i < countNotNullField; i++) { sql.append("?,"); } sql.setCharAt(sql.length() - 1, ')'); executeDML(sql.toString(), params.toArray()); } /** * 删除clazz表示类对应的表中的记录(指定主键id的记录) * 把对象中不为null的属性往数据库中存储!如果数字为null则放0 * * @param clazz 跟表对应的类的Class对象 * @param id 主键的值 */ // delete from User where id = 2; public void delete(Class clazz, Object id) { // Emp.class, 2 --> delete from emp where id=2 // 通过Class对象找TableInfo TableInfo tableInfo = TableContext.poClassTableMap.get(clazz); // 获得主键 ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey(); String sql = "delete from " + tableInfo.getTname() + " where " + onlyPriKey.getName() + "=?;"; executeDML(sql, new Object[]{id}); } /** * 删除对象在数据库中对应的记录(对象所在类对应到表,对象的主键对应到的记录) * * @param obj */ public void delete(Object obj) { Class c = obj.getClass(); TableInfo tableInfo = TableContext.poClassTableMap.get(c); // 获得主键 ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey(); // 通过反射机制,调用属性对应的get方法或set方法 Object priKeyValue = ReflectUtils.invokeGet(onlyPriKey.getName(), obj); delete(obj.getClass(), priKeyValue); } /** * 更新对象对应的记录,并且只更新指定的字段的值 * * @param obj 索要更新的对象 * @param fieldNames 更新的属性列表 * @return 执行sql语句后影响记录的行数 */ // update user set uname=?, pwe=? public int update(Object obj, String[] fieldNames) { // obj{"uname", "pwd} --> update 表名 set uname=?, pwd=? where id=? Class c = obj.getClass(); List<Object> params = new ArrayList<>(); TableInfo tableInfo = TableContext.poClassTableMap.get(c); ColumnInfo priKey = tableInfo.getOnlyPriKey(); StringBuilder sql = new StringBuilder("update " + tableInfo.getTname() + " set "); for (String fname : fieldNames) { Object fvalue = ReflectUtils.invokeGet(fname, obj); params.add(fvalue); sql.append(fname + "=?,"); } sql.setCharAt(sql.length() - 1, ' '); sql.append(" where "); sql.append(priKey.getName() + "=?"); params.add(ReflectUtils.invokeGet(priKey.getName(), obj)); return executeDML(sql.toString(), params.toArray()); } /** * 查询返回多行记录,并将每行记录封装到clazz指定的类的对象中 * * @param sql 查询语句 * @param clazz 封装数据的javabean类的Class对象 * @param params sql的参数 * @return 返回查询到的结果 */ public List queryRows(final String sql, final Class clazz, final Object[] params) { // 存放查询结果的容器 return (List) executeQueryTemplate(sql, params, clazz, new CallBack() { @Override public Object doExecute(Connection conn, PreparedStatement ps, ResultSet rs) { List list = null; try { ResultSetMetaData metaData = rs.getMetaData(); // 多行 while (rs.next()) { if (list == null) { list = new ArrayList(); } // 调用javabean的无参构造器 Object rowObj = clazz.newInstance(); // 多列 select username, pwd, age from user where id>? and age>? for (int i = 0; i < metaData.getColumnCount(); i++) { // username String columnName = metaData.getColumnLabel(i + 1); Object columnValue = rs.getObject(i + 1); // 调用rowObj对象的setUsername(String uname)方法,将columnValue的值设置进去 ReflectUtils.invokeSet(rowObj, columnName, columnValue); } list.add(rowObj); } } catch (Exception e) { e.printStackTrace(); } return list; } }); } /** * 查询返回一行记录,并将该记录封装到clazz指定的类的对象中 * * @param sql 查询语句 * @param clazz 封装数据的javabean类的Class对象 * @param params sql的参数 * @return 返回查询到的结果 */ public Object queryUniqueRows(String sql, Class clazz, Object[] params) { List list = queryRows(sql, clazz, params); return (list != null || list.size() > 0) ? list.get(0) : null; } /** * 根据主键的值直接查找对应的对象 * @param clazz * @param id * @return */ public Object queryById(Class clazz, Object id){ // select * from emp where id=? TableInfo tableInfo = TableContext.poClassTableMap.get(clazz); ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey(); String sql = "select * from " + tableInfo.getTname() + " where " + onlyPriKey.getName() + "=?"; return queryUniqueRows(sql, clazz, new Object[]{id}); } /** * 查询返回一个值(一行一列),并将该值返回 * * @param sql 查询语句 * @param params sql的参数 * @return 返回查询到的结果 */ public Object queryValue(String sql, Object[] params) { return executeQueryTemplate(sql, params, null, new CallBack() { @Override public Object doExecute(Connection conn, PreparedStatement ps, ResultSet rs) { Object value = null; try { // 多行 while (rs.next()) { // select count(*) from user value = rs.getObject(1); } } catch (Exception e) { e.printStackTrace(); } return value; } }); } /** * 查询返回一个数字(一行一列),并将该值返回 * * @param sql 查询语句 * @param params sql的参数 * @return 返回查询到的数字 */ public Number queryNumber(String sql, Object[] params) { return (Number) queryValue(sql, params); } /** * 分页查询 * * @param pageNum 第几页数据 * @param size 每页显示多少记录 * @return */ public abstract Object queryPagenate(int pageNum, int size); @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
  • MysqlQuery
    package com.sxt.SORM.core; import com.sxt.SORM.po.Emp; import com.sxt.SORM.vo.EmpVO; import java.util.List; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: MysqlQuery.java * @time: 2020/3/13 16:54 * @desc: |负责针对mysql数据库的查询 */ public class MysqlQuery extends Query { public static void main(String[] args) { Object obj = new MysqlQuery().queryValue("select count(*) from emp where salary>?", new Object[]{1000}); System.out.println(obj); } /** * 复杂多行查询测试 */ public static void testQueryRows() { List<Emp> list = new MysqlQuery().queryRows("select id,empname,age from emp where age>? and salary<?", Emp.class, new Object[]{1, 9000}); for (Emp e : list) { System.out.println(e.getEmpname()); } String sql2 = "select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e" + " " + "join dept d on e.deptId=d.id;"; List<EmpVO> list2 = new MysqlQuery().queryRows(sql2, EmpVO.class, null); for (EmpVO e : list2) { System.out.println(e.getEmpname() + "-" + e.getDeptAddr() + "-" + e.getXinshui()); } } /** * 增删改操作测试 */ public static void testDML() { Emp e = new Emp(); e.setEmpname("Tom"); e.setBirthday(new java.sql.Date(System.currentTimeMillis())); e.setAge(30); e.setSalary(8888.0); e.setId(1); // new MysqlQuery().delete(e); // new MysqlQuery().insert(e); new MysqlQuery().update(e, new String[]{"empname", "age", "salary"}); } @Override public Object queryPagenate(int pageNum, int size) { return null; } }

9.3 工厂模式+单例模式+克隆模式构建QueryFactory

  • 修改db.properties,增加queryClass的路径
    queryClass=com.sxt.SORM.core.MysqlQuery
  • 修改Configuration,增加queryClass的属性,getset方法
    // 项目使用的查询类的路径 private String queryClass; public String getQueryClass() { return queryClass; } public void setQueryClass(String queryClass) { this.queryClass = queryClass; }
  • Query实现implements Cloneable接口
    public abstract class Query implements Cloneable protected Object clone() throws CloneNotSupportedException { return super.clone(); }
  • QueryFactory
    package com.sxt.SORM.core; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: QueryFactory.java * @time: 2020/3/11 13:33 * @desc: |创建Query对象的工厂类:单例+克隆+工厂 */ public class QueryFactory { private static QueryFactory factory = new QueryFactory(); // 原型对象 private static Query prototypeObj; static{ try { // 加载指定的Query类 Class c = Class.forName(DBManager.getConf().getQueryClass()); prototypeObj = (Query) c.newInstance(); } catch (Exception e) { e.printStackTrace(); } } // 私有构造器 private QueryFactory(){} public static Query createQuery(){ try { return (Query) prototypeObj.clone(); } catch (Exception e) { e.printStackTrace(); return null; } } }
  • 客户端调用测试类
    package com.sxt.SORM.test; import com.sxt.SORM.core.MysqlQuery; import com.sxt.SORM.core.Query; import com.sxt.SORM.core.QueryFactory; import com.sxt.SORM.vo.EmpVO; import java.util.List; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Test.java * @time: 2020/3/17 12:55 * @desc: |客户端调用测试类 */ public class Test { public static void main(String[] args){ Query q = QueryFactory.createQuery(); String sql2 = "select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e" + " " + "join dept d on e.deptId=d.id;"; List<EmpVO> list2 = q.queryRows(sql2, EmpVO.class, null); for (EmpVO e : list2) { System.out.println(e.getEmpname() + "-" + e.getDeptAddr() + "-" + e.getXinshui()); } } }

9.4 增加连接池提高效率

  • Connection Pool
  • 就是将Connection对象放入List中,反复重用!
  • 连接池的初始化:事先放入多个连接对象
  • 从连接池中取连接对象
  • 如果池中有可用连接,则将池中最后一个返回。同时,讲该连接从池中remove,表示正在使用。
  • 如果池中无可用连接,则创建一个新的。
  • 关闭连接
  • 不是真正关闭连接,而是将用完的连接放入连接池中。
  • 市面上的连接池产品:
  • DBCP
  • c3p0
  • proxool
  • 代码
  • 配置文件新增连接池参数
    poolMinSize=10 poolMaxSize=100
  • Configuration增加上述参数的属性和getset方法
    // 连接池最小限制 private int poolMinSize; // 连接池最大限制 private int poolMaxSize; public int getPoolMinSize() { return poolMinSize; } public void setPoolMinSize(int poolMinSize) { this.poolMinSize = poolMinSize; } public int getPoolMaxSize() { return poolMaxSize; } public void setPoolMaxSize(int poolMaxSize) { this.poolMaxSize = poolMaxSize; }
  • 新增连接池类:DBConnPool
    package com.sxt.SORM.pool; import com.sxt.SORM.core.DBManager; import java.sql.Connection; import java.util.ArrayList; import java.util.List; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: DBConnPool.java * @time: 2020/3/17 14:03 * @desc: |连接池的类 */ public class DBConnPool { // 最大连接数 private static final int POOL_MAX_SIZE = DBManager.getConf().getPoolMaxSize(); // 最小连接数 private static final int POOL_MIN_SIZE = DBManager.getConf().getPoolMinSize(); // 连接池对象 private List<Connection> pool; public DBConnPool() { initPool(); } /** * 初始化连接池,使池中的连接数达到最小值 */ public void initPool() { if (pool == null) { pool = new ArrayList<Connection>(); } while (pool.size() < DBConnPool.POOL_MIN_SIZE) { pool.add(DBManager.createConn()); System.out.println("初始化池,池中连接数:" + pool.size()); } } /** * 从连接池中取出一个连接 */ public synchronized Connection getConnection() { int last_index = pool.size() - 1; // 获得一个连接 Connection conn = pool.get(last_index); pool.remove(last_index); return conn; } /** * 将连接放回池中 * * @param conn */ public synchronized void close(Connection conn) { if (pool.size() >= POOL_MAX_SIZE) { try { if (conn != null) { conn.close(); } } catch (Exception e) { e.printStackTrace(); } } else { pool.add(conn); } } }
  • 修改DBManager方法,把原来获取连接的方式,改为使用连接池,关闭连接的方式也做同样的修改
    package com.sxt.SORM.core; import com.sxt.SORM.bean.Configuration; import com.sxt.SORM.pool.DBConnPool; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: DBManager.java * @time: 2020/3/11 13:43 * @desc: |根据配置信息,维持连接对象的管理(增加连接池功能) */ public class DBManager { // 配置信息 private static Configuration conf; // 连接池对象 private static DBConnPool pool; static { // 静态代码块 Properties pros = new Properties(); try { pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/sxt/SORM/db.properties")); } catch (IOException e) { e.printStackTrace(); } conf = new Configuration(); conf.setDriver(pros.getProperty("driver")); conf.setPoPackage(pros.getProperty("poPackage")); conf.setPwd(pros.getProperty("pwd")); conf.setSrcPath(pros.getProperty("srcPath")); conf.setURL(pros.getProperty("URL")); conf.setUser(pros.getProperty("user")); conf.setUsingDb(pros.getProperty("usingDB")); conf.setQueryClass(pros.getProperty("queryClass")); conf.setPoolMaxSize(Integer.parseInt(pros.getProperty("poolMaxSize"))); conf.setPoolMinSize(Integer.parseInt(pros.getProperty("poolMinSize"))); // 加载TableContext System.out.println(TableContext.class); } public static Connection getConn() { /*获取数据库(mysql)驱动连接*/ // 加载驱动类 /* 第一种方法:直接取 try { Class.forName(conf.getDriver()); // 直接建立连接,后期增加连接池处理,提高效率! return DriverManager.getConnection(conf.getURL(), conf.getUser(), conf.getPwd()); } catch (Exception e) { e.printStackTrace(); return null; } */ // 第二种方法,通过连接池 if(pool == null){ pool = new DBConnPool(); } return pool.getConnection(); } /** * 创建新的Connection连接 * * @return */ public static Connection createConn() { /*获取数据库(mysql)驱动连接*/ // 加载驱动类 try { Class.forName(conf.getDriver()); // 直接建立连接,后期增加连接池处理,提高效率! return DriverManager.getConnection(conf.getURL(), conf.getUser(), conf.getPwd()); } catch (Exception e) { e.printStackTrace(); return null; } } public static void close(ResultSet rs, Statement ps, Connection conn) { /**关闭接口方法*/ try { if (rs != null) { rs.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (ps != null) { ps.close(); } } catch (Exception e) { e.printStackTrace(); } try { // if (conn != null) { // conn.close(); // } pool.close(conn); } catch (Exception e) { e.printStackTrace(); } } public static void close(Statement ps, Connection conn) { /**关闭接口方法,重载*/ try { if (ps != null) { ps.close(); } } catch (Exception e) { e.printStackTrace(); } try { // if (conn != null) { // conn.close(); // } pool.close(conn); } catch (Exception e) { e.printStackTrace(); } } public static void close(Connection conn) { /*关闭接口方法,重载*/ try { // if (conn != null) { // conn.close(); // } pool.close(conn); } catch (Exception e) { e.printStackTrace(); } } public static Configuration getConf() { return conf; } }
  • 客户端测试
    package com.sxt.SORM.test; import com.sxt.SORM.core.Query; import com.sxt.SORM.core.QueryFactory; import com.sxt.SORM.vo.EmpVO; import java.util.List; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Test2.java * @time: 2020/3/17 16:40 * @desc: |测试连接池的调用效率 */ public class Test2 { public static void test1(){ Query q = QueryFactory.createQuery(); String sql2 = "select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e" + " " + "join dept d on e.deptId=d.id;"; List<EmpVO> list2 = q.queryRows(sql2, EmpVO.class, null); for (EmpVO e : list2) { System.out.println(e.getEmpname() + "-" + e.getDeptAddr() + "-" + e.getXinshui()); } } public static void main(String[] args){ long a = System.currentTimeMillis(); for (int i = 0; i < 3000; i++) { test1(); } long b = System.currentTimeMillis(); // 不加连接池的耗时:13077ms,增加连接池之后,耗时为2478 System.out.println(b-a); } }

9.5 jar包和API文档的生成

  • 其中编码需要改为:-encoding utf-8 -charset utf-8
  • 测试使用
  • 配置配置文件
    usingDB=mysql URL=jdbc:mysql://localhost:3306/sorm?serverTimezone=UTC driver=com.mysql.cj.jdbc.Driver user=root pwd=123456 srcPath=F:/BookStudy/else/SORMDemo/src poPackage=po queryClass=com.sxt.SORM.core.MysqlQuery poolMinSize=10 poolMaxSize=100
  • 测试po类的生成,增删改查
    package test; import com.sxt.SORM.core.Query; import com.sxt.SORM.core.QueryFactory; import po.Emp; /** * @author: Li Tian * @contact: litian_cup@163.com * @software: IntelliJ IDEA * @file: Test.java * @time: 2020/3/17 17:38 * @desc: | */ public class Test { public static void main(String[] args) { // 通过这个方法可以生成po类 // TableContext.updateJavaPOFile(); // add(); // select(); // delete(); update(); } public static void add() { // 测试插入对象 Emp e = new Emp(); e.setAge(18); e.setEmpname("我"); e.setSalary(2000.0); Query q = QueryFactory.createQuery(); q.insert(e); } public static void delete(){ // 测试删除对象 Emp e = new Emp(); e.setId(12); Query q = QueryFactory.createQuery(); q.delete(e); } public static void update(){ // 测试删除对象 Emp e = new Emp(); e.setId(1); e.setAge(1); Query q = QueryFactory.createQuery(); q.update(e, new String[]{"age"}); } public static void select(){ // 测试查询 Query q = QueryFactory.createQuery(); Number n = q.queryNumber("select count(*) from emp where salary>?", new Object[]{100}); System.out.println(n); } }
  • 关于连表查询,我看操作实在是过于复杂,还要自己建立EmpVO,想想就算了。