文章目录
- 1.四个接口:左java.sql包下的DCSR,右com.mysql.jdbc.Driver
- 2.释放资源:finally
- 3.工具类封装:new Properties(),省内存
- 4.总结: SPI (Service Provider Interface), API (Application Programming Interface)
- 5.JDBC事务操作:conn.setAutoCommit(false)
- 6.登陆案例: ' " +name+ " '
- 7.登陆案例的预编译改造:PreparedStatement,setString,executeQuery
- 8.c3p0连接池:自动cpds.set
- 9.类加载器路径:is = classLoader.getResourceAsStream()
- 10.druid连接池:自动ds.set
- 11.封装druid连接池工具类:ds = DruidDataSourceFactory
- 12.execute/update方法:template =
- 13.queryForXX方法:Map.Entry < String, Object > ,Map < String, Object > 两个数据类型
- 14.query方法:获取类并反射得到方法,rowMapper作为参数传入query方法
- 15.update/query方法源码和测试封装:resultSet.getInt()
- 16.update/query方法封装:pstm.setObject,rm = new MyRowMapper(){}
1.四个接口:左java.sql包下的DCSR,右com.mysql.jdbc.Driver
如下jdbc是父类引用。
如下mchange. .和c3p0. .一起。第一个导入的是mysql-connector…。
如下代码第一行new Driver()是导入com.mysql.jdbc(用mysql实现好的,就是上面导入的mysql-connector..jar包)
而不是java.sql(自己不会重写抽象方法)下,参数是接口类型需要传入接口的实现类对象即new Driver()。
registerDriver相当于set方法,get获取的是mysql.Driver.connect方法返回的Connection类即com.mysql.jdbc.JDBC4Connection(有mysql.的都是导入的jar包)。
package com.itheima01.jdbc;
import com.mysql.jdbc.Driver;
import java.sql.*;
public class JdbcDemo {
public static void main(String[] args) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
//1. 注册驱动 (注意: 导入的mysql的驱动)
/*
* A. 查看mysql.Driver源码: 点new Driver()中Driver看源码
* 发现static 代码块里 已经注册了驱动 -> 驱动自注册 相当于set一次就行
* 带来问题: 外部的注册没有意义(重复注册)
*
* 解决: 保证mysql.Driver类被加载(静态代码块就会执行),如下两种方案:
* 1. 创建对象
* 2. 反射:a. 节省内存
* b. 跟驱动包的关联只剩一个字符串:"com.mysql.jdbc.Driver"
* 待会将字符串写入配置文件,只要改配置文件就行 就会跟 mysql驱动包的彻底解耦
*/
//111111111111111111111111111111111111111111111111111111111111111111111111111111111111
// DriverManager.registerDriver(new Driver()); //这行重复注册,下行new了就会加载 源码里的静态代码块,所以这行=下行
//new Driver(); // Class对象 + 普通实例 //只要用了这个类,这个类就会被加载内存中方法区 //自动导包
// new Driver()的内存消耗等价于下面两行: 其实只需要calss对象,不需要实例
// Class<?> clazz = Class.forName("com.mysql.jdbc.Driver"); //获取这个类的class对象
// Object obj = clazz.newInstance(); //利用class对象调用其空参构造来创建一个实例
Class.forName("com.mysql.jdbc.Driver"); //获取Class对象,没有普通实例,因为普通实例没有意义 //用反射,com.mysql.jdbc.Driver这个类也会被加载
//111111111111111111111111111111111111111111111111111111111111111111111111111111111111
//2. 获取连接
/*
* 只要涉及两个软件通信 : 网络三要素(必要非充分:一定要,但是有它们三不一定够如多了资源位置)
* 1. 协议 : jdbc:mysql (主协议:子协议)
* 2. ip : 数据库所在的计算机(自己:localhost或127.0.0.1)
* 3. port : mysql数据库3306
* 资源位置: 数据仓库的名称
*
* 协议://ip:port/资源位置
* https://www.baidu.com:443
*/
// String url = "jdbc:mysql://localhost:3306/day03"; //day03是数据库
String url = "jdbc:mysql:///day03"; //ip:localhost port:3306 可以省略
String user = "root";
String pwd = "1234";
Connection conn = DriverManager.getConnection(url, user, pwd);
System.out.println("conn:" + conn); //引用类型打印地址
//1111111111111111111111111111111111111111111111111111111111111111111111111111111111
//3. 创建执行sql的语句对象
/*
* Connection 接口的一些方法
* <1>. Statement createStatement(); 创建执行sql的语句对象,相当于创建一个流
* <2>. PreparedStatement prepareStatement(sql); 创建一个预编译sql的语句对象
* <3>. 事务操作相关
*/
Statement statement = conn.createStatement();
System.out.println("statement:" + statement);
//11111111111111111111111111111111111111111111111111111111111111111111111111111111111
//4. 执行sql,返回结果
/*
* Statetment 接口的api
* 1. ResultSet statement.executeQuery(sql);
* 执行的查询语句 : DQL
* 返回的查询结果: 结果集
*
* 2. int executeUpdate(sql) :
* 执行的增删改语句: DML
* 返回的结果: 被影响的行数
*
* 3. boolean execute(sql); -> 不需要掌握,知道即可
* 万能 : DDL等 如create成功或失败是和异常相关,和返回值无关
* 返回值: 非查询语句返回false,查询语句返回true
*/
String sql = "select * from emp";
ResultSet resultSet = statement.executeQuery(sql);
//11111111111111111111111111111111111111111111111111111111111111111111111111111111111
//5. 处理结果
while(resultSet.next()){
String name = resultSet.getString("name");
// String id = resultSet.getString("id"); //也可以,java程序以外的所有数据对java来说都是字符串
int id = resultSet.getInt("id"); //底层先调用getString再parse int
System.out.println(id+ ":" + name);
}
//11111111111111111111111111111111111111111111111111111111111111111111111111111111111
//6. 释放资源
resultSet.close();
statement.close();
conn.close();
}
}
resultset不是返回一行数据,而是带有id=…。因为返回一行数据如 11孙悟空男,不知道怎么解析。
如下hasNext和next区别。
2.释放资源:finally
package com.itheima04.release;
import com.itheima05.utils.JdbcUtil;
import java.io.Closeable;
import java.io.IOException;
import java.sql.*;
public class ReleaseDemo {
public static void main(String[] args) {
ResultSet resultSet = null;
Statement statement = null;
Connection conn = null;
try {
/*Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/day03";
String user = "root";
String pwd = "1234";
conn = DriverManager.getConnection(url, user, pwd);*/
conn = JdbcUtil.getConnection(); //查询要获取连接,getConnection方法调用多遍,所以getConnection方法不写try catch,查询提示 查询失败,删除提示 删除失败,封装时不知道是查询还是删除,不好提示,所以往外抛。
conn = JdbcUtil.getConnection(); //增删改也要获取连接
//1111111111111111111111111111111111111111111111111111111111111111111111111
statement = conn.createStatement();
String sql = "select * from emp";
resultSet = statement.executeQuery(sql);
while(resultSet.next()){
String name = resultSet.getString("name");
System.out.println(name);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
/* if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}*/
// closeIo(resultSet,statement,conn);
JdbcUtil.release(conn,statement,resultSet);
}
}
//1111111111111111111111111111111111111111111111111111111111111111111111
private static void closeIo(AutoCloseable... ios) { //AutoCloseable接口位于java.lang包下,不用导包
for (AutoCloseable io : ios) {
if(io != null){
try {
io.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
3.工具类封装:new Properties(),省内存
package com.itheima05.utils;
import java.sql.*;
/**
* 工具类: 0. 拥有很多 工具方法(重复的代码封装) 的类
* 命名规范: utils 包 -> xxUtil 类 (xx : 某个模块的名称)
* Objects,Arrays,Collections...(JDK提供的)
*
* 1. 一般工具类中方法是静态的,不用实例化,节省内存
*
* 2. 封装方法的步骤
* 1. 先把要把封装的代码写出来
* 2. 观察不断重复的部分
* 3. 定义方法,然后直接复制过来
* 4. 设置参数和返回值
*
* 注意点: 1. 扩展性 : 不要导入mysql包中的类, 要导入java包中的类(这样换成oracle也可用)
* 2. 工具类中的异常一般是往外抛 : 一般异常是要在业务中处理
*/
public class JdbcUtil {
static{
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//1111111111111111111111111111111111111111111111111111111111111111111111111111111
public static Connection getConnection() throws SQLException {
//此方法会被多次调用,注册驱动只需要一次 -> 所以用静态代码块 如上
// Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/day03";
String user = "root";
String pwd = "1234";
Connection conn = DriverManager.getConnection(url, user, pwd);
return conn;
}
//1111111111111111111111111111111111111111111111111111111111111111111111111111111
/**
* 文档注释: a. 写在类上面 : 描述类的用途
* b. 写在方法上面 : 描述方法的用途 (返回值,参数)
*/
public static void release(Connection conn, Statement statement, ResultSet resultSet){
//java.sql.Connection
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
如下改进上面工具类。
/*
* 问题: 驱动,url,用户名和密码等信息 是有可能会变的, 变动频率比较低
* 1. 如果不变,直接写死在代码中
* 2. 变,但是频率高 : 一般设置成参数
* 3. 变,但是频率不高: 放在配置文件
* 1. 解耦 : 信息要改变的话,只要改配置文件,代码不用改,程序不需要重新编译和部署
* 2. 代码简化 : 无需调用的时候传参了
*/
//jdbc.properties文件,每个月改一次 //文件里没有关键字,也没有双引号,本来就是字符串
driverName = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/day03
user = root
pwd = 1234
package com.itheima05.utils;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtil02 {
static String driverName;
static String url;
static String user;
static String pwd;
static{
try {
Properties p = new Properties();
p.load(new FileInputStream("src/jdbc.properties"));
driverName = p.getProperty("driverName");
url = p.getProperty("url");
user = p.getProperty("user");
pwd = p.getProperty("pwd");
Class.forName(driverName);
} catch (Exception e) {
e.printStackTrace();
}
}
//1111111111111111111111111111111111111111111111111111111111111111111111111111111111
public static Connection getConnection() throws SQLException {
Connection conn = DriverManager.getConnection(url, user, pwd);
return conn;
}
//11111111111111111111111111111111111111111111111111111111111111111111111111111111111
public static void release(Connection conn, Statement statement, ResultSet resultSet){
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
4.总结: SPI (Service Provider Interface), API (Application Programming Interface)
JDBC是java的数据库连接规范
,里面有两个重要接口:Driver接口
定义了一个connect方法,也就是说你通过一个第三方数据库的Driver,运行它的connect方法(输入连接的字符串)就能得到一个Connection类型的对象。
Connection本身也是一个接口,Connection中定义了许多具体的执行sql方法,也就是我们真正操作数据库最核心的接口类就是Connection接口
。它的方式就是各家的数据库分别去实现一系列JDBC定义的接口中的方法。
平时开发只是实现接口,并没有涉及到打破双亲和SPI,那JDBC用到这两者带来哪些便利?如下右边的List放的是h2等数据库的driver。
1.
如下是接口的开发模式,没涉及打破双亲委派类加载机制和SPI,是jdbc早期的开发方式。h2的Driver是实现了JDBC的Driver接口的,直接new Driver().connect。
2.
如下DriverManager是java.sql中类,整段代码没有出现h2类,却能根据url判断需要用h2的jar包进行连接并连接成功,这怎么做到的呢?因为DriverManager中List存了很多数据库的Driver,class.forName强制把h2.Driver加载进List中
。DriverManager是怎么统计到h2和mysql两个实现jdbc的类的?这就用到了SPI。
如下SPI(Service Provide Interface服务提供接口)创建:文件名就是需要实现的接口的全限定名即java.sql.Driver 。第一个java.sql.Driver文件里写的是org.h2.Driver(写的是具体实现java.sql.Driver接口是哪个类即org.h2.Driver),第二个java.sql.Driver写的是com.mysql.cg.jdbc.Driver。
3.
打破双亲类加载机制(线程上下文类加载器,在加载DriverManager这个类用到打破双亲类加载):DriverManager是jdk自带的类,DriverManager类使用的是bootstrap引用类加载器
。数据库是用户类用bootstrap加载不合适,所以DriverManager去加载h2的Driver需要把当前引用类加载器替换为当前系统或当前线程的应用app类加载器
。
5.JDBC事务操作:conn.setAutoCommit(false)
package com.itheima01.transaction;
import com.itheima.utils.JdbcUtil;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
// JDBC : 写一个转账案例
public class Demo {
public static void main(String[] args) {
System.out.println("请输入转出的账户:");
Scanner sc = new Scanner(System.in); // 控制台: 模拟页面
String outUser = sc.nextLine();
System.out.println("请输入转入的账户:");
String inUser = sc.nextLine();
System.out.println("请输入转账的金额:");
double money = sc.nextDouble();
//sql里面最好写单引号 , 1000和jack改为 两个双引和两个+号
//String sql1 = "update account set money = money-1000 where name = 'jack'";
String sql1 = "update account set money = money-"+money+" where name = '"+outUser+"'";
String sql2 = "update account set money = money+"+money+" where name = '"+inUser+"'";
//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111
Connection conn = null;
try {
/*
* 事务操作: Connection
* 1. setAutoCommit(false); 开启事务
* 2. commit(); 提交事务
* 3. rollback(); 事务回滚
* 注意点: 在事务中, 开启事务的连接才具有手动提交事务的功能
* 一组操作都必须要同一个 连接conn 要执行
*/
conn = JdbcUtil.getConnection(); //访问数据库,try外面定义conn
// Connection conn2 = JdbcUtil.getConnection();
conn.setAutoCommit(false); //开启事务,禁止自动提交
Statement statement = conn.createStatement();
statement.executeUpdate(sql1); //转出
// int i = 1/0; // ArithmeticException 算术异常 模拟银行爆炸
Statement statement2 = conn.createStatement();
statement2.executeUpdate(sql2); //转入
//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
conn.commit(); //提交事务(和事务回滚只有其一执行)
System.out.println("转账成功~~");
} catch (Exception e) { // 注意: 提升异常级别(用于捕获算术异常)
e.printStackTrace();
if(conn != null){ //Connection conn放外面,这边访问的到
try {
conn.rollback(); //事务回滚
} catch (SQLException e1) {
e1.printStackTrace();
}
}
System.out.println("转账失败");
}
}
}
6.登陆案例: ’ " +name+ " ’
package com.itheima02.login;
import com.itheima.utils.JdbcUtil;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
/*
前提: 用户能够登录,说明已经注册过了
注册成功的时候, 程序会把用户的信息保存到数据库
* 登录案例: 逻辑: 请输入用户名和密码(用户)
* 我们: 校验数据库
* sql : select * from account where name = ? and pwd = ?; (name用户名唯一)
* 预测结果: 1. 0条 : 用户名不存在或密码错误
* 2. 1条 : 登录成功
*/
public class LoginDemo {
public static void main(String[] args) throws SQLException {
System.out.println("请输入用户名:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
System.out.println("请输入密码:");
String pwd = sc.nextLine();
String sql = "select * from account where name = '"+name+"' and pwd = '"+pwd+"'";
System.out.println(sql); //将参数(上面键盘输入)直接拼接进sql
//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
Connection conn = JdbcUtil.getConnection();
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(sql); //将sql发送给数据库去处理
if(resultSet.next()){ //有一条则为true
System.out.println("登录成功~~");
}else{
System.out.println("用户名不存在或密码错误");
}
}
}
根据(用户名和密码)或(1=1永真)为条件查询数据库,what可以随便写。
7.登陆案例的预编译改造:PreparedStatement,setString,executeQuery
预编译知道了sql的语法结构了,已经把关键字认全了,后面再包含关键字or
就不认了(当成字符串处理),可以防止sql注入
。
package com.itheima02.login;
import com.itheima.utils.JdbcUtil;
import java.sql.*;
import java.util.Scanner;
public class LoginDemo02 {
public static void main(String[] args) throws SQLException {
System.out.println("请输入用户名:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
System.out.println("请输入密码:");
String pwd = sc.nextLine();
// 1. 改造sql
String sql = "select * from account where name = ? and pwd = ?";
System.out.println(sql);
Connection conn = JdbcUtil.getConnection();
// 2. 预编译sql // PreparedStatement 是 Statement的子接口
PreparedStatement statement = conn.prepareStatement(sql);
// 3. 设置参数
// setString(int parameterIndex, String x)
// parameterIndex : sql中的?的索引(从1开始,从左开始) // String x: 参数
statement.setString(1,name);
statement.setString(2,pwd);
// 4. 传参执行
ResultSet resultSet = statement.executeQuery();
if(resultSet.next()){
System.out.println("登录成功~~");
}else{
System.out.println("用户名不存在或密码错误");
}
}
}
如下手动在数据库中增加一行。
package com.itheima03.prepare;
import com.itheima.utils.JdbcUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
/*
* 预编译的好处: 1. 防止sql注入
* 2. 阅读性强(sql)
* 3. 批量处理sql,效率高 (节省了sql重复编译过程)
*/
public class PrepareDemo {
public static void main(String[] args) throws SQLException {
// method01(); //用预编译,如下不用预编译
String name = "ww";
int money = 200;
String sql1 = "insert into account values(null,'"+name+"',"+money+",null)";
String name2 = "www";
int money2 = 2002;
String sql2 = "insert into account values(null,'"+name2+"',"+money2+",null)";
Connection conn = JdbcUtil.getConnection();
Statement statement = conn.createStatement(); //不用预编译
statement.executeUpdate(sql1);// 编译+运行
statement.executeUpdate(sql2);//编译+运行,和上行一共两次编译
statement.close();
conn.close();
}
//1111111111111111111111111111111111111111111111111111111111111111111111111
private static void method01() throws SQLException {
String sql = "insert into account values(null,?,?,null)";
Connection conn = JdbcUtil.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setString(1,"zs");
pstm.setDouble(2,1000);
pstm.executeUpdate();//运行
pstm.setString(1,"ls");
pstm.setDouble(2,2000);
pstm.executeUpdate();//运行
pstm.close();
conn.close();
}
}
8.c3p0连接池:自动cpds.set
jdbc2.0才引进连接池,不是线程池(连接池的技术标准就是DataSource替代DriverManager)。
package com.itheima04.c3p0;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class C3p0Demo {
public static void main(String[] args) throws PropertyVetoException, SQLException {
// method01(); //这行方法和下面xml文件无关
/*
* 配置文件方式 : 默认情况下, c3p0将会在 类加载器路径(在当前的工程下, src路径)下
* 寻找一个名为c3p0-config.xml 配置文件(xml文件是复杂的Properties文件,也是key-value)
*
* 套路: 1. 在src下创建名为c3p0-config.xml配置文件(内容直接复制)
* 2. 创建ComboPooledDataSource核心类
*/
ComboPooledDataSource cpds = new ComboPooledDataSource();
//1. 底层自动会去类加载器路径(写代码:src下) 去寻找一个名为c3p0-config.xml 配置文件
// 2. 自动解析: 读取xml中配置信息 , 设置给c3p0即cpds
String sql = "select * from account"; // 同下面
Connection conn = cpds.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
ResultSet resultSet = pstm.executeQuery();
while(resultSet.next()){
String name = resultSet.getString("name");
System.out.println(name + "--");
}
conn.close();// 将连接还给连接池
cpds.close(); // 销毁
}
//111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
private static void method01() throws PropertyVetoException, SQLException {
// 如下硬编码: 用代码来实现参数的设置。一般不用硬编码,用配置文件
ComboPooledDataSource cpds = new ComboPooledDataSource();//ComboPooledDataSource是DataSource实现类
cpds.setDriverClass( "com.mysql.jdbc.Driver"); //需要mysql-connector-java-x.x.x-bin.jar
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/day05" );
cpds.setUser("root");
cpds.setPassword("1234");
String sql = "select * from account";
Connection conn = cpds.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
ResultSet resultSet = pstm.executeQuery();
while(resultSet.next()){
String name = resultSet.getString("name");
System.out.println(name); //System.err.println(name);//红色
}
conn.close();// 将连接还给连接池
cpds.close(); // 销毁
}
}
//c3p0-config.xml
<c3p0-config>
<default-config> <!-- 使用默认的配置读取连接池对象 -->
<!-- 如下连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day05</property>
<property name="user">root</property>
<property name="password">1234</property>
<!-- 如下连接池参数 -->
<!--
initialPoolSize : 初始化连接数 3
maxPoolSize: 最大连接数 5
checkoutTimeout : 连接超时时间 2000ms(默认10s,访问数超过最大连接数, 有人必须要等,2秒连不上给个提示或报错)
maxIdleTime :最大的闲置时间,连接超过maxIdleTime没人使用闲置就销毁maxPoolSize中连接,留到minPoolSize中数量,因为费内存
-->
<property name="initialPoolSize">3</property>
<property name="maxPoolSize">5</property>
<property name="minPoolSize">2</property>
<property name="checkoutTimeout">2000</property>
<property name="maxIdleTime">1000</property>
</default-config>
<!--111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111-->
<named-config name="xx">
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/beitai</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">15</property>
<property name="checkoutTimeout">2000</property>
<property name="maxIdleTime">1000</property>
</named-config>
</c3p0-config>
9.类加载器路径:is = classLoader.getResourceAsStream()
package com.itheima04.c3p0;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ParseDemo {
@Test
public void method01() throws IOException {
Properties p = new Properties(); //Properties就是一个map
p.load(new FileInputStream("src/jdbc.properties")); //相对路径:当前工程src下
String url = p.getProperty("url"); //.properties文件中有url=...
System.out.println(url);
}
@Test
public void method02() throws IOException {
/*
* 1. 类加载器 classloader 【底层: 输入流】
* 作用: 将 .class文件(硬盘) 加载进内存(兼职把jdbc.properties加载进来)。
*
* 2. classloader怎么知道.class文件在哪里?
* classloader有个默认加载路径:out路径下项目名路径(src和out/production/项目名路径里文件全一样)。
*
* 相比method01,一般用method02更通用,因为每个工程都会有类加载器路径,但是每个工程的相对路径不一定是当前工程src下。
* 如web阶段main方法不在项目里,每个项目的入口是main方法,main方法在tomcat里,
* 所以工程的相对路径会变,但是类加载器的路径不变,一直指向.class文件路径。
*/
ClassLoader classLoader = ParseDemo.class.getClassLoader(); //利用当前类获取类加载器路径
InputStream is = classLoader.getResourceAsStream("jdbc.properties"); //获取资源转成流
//此时类加载器工作的路径: out/production/jdbc-day05【即类加载器路径】,所以不用写src/jdbc.properties
Properties p = new Properties();
p.load(is);
String url = p.getProperty("url");
System.out.println(url + "-2");
}
}
如下是url + “-2”。
package com.itheima04.c3p0;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.SQLException;
//c3p0-config.xml验证
public class C3p0Demo02 {
public static void main(String[] args) throws SQLException {
ComboPooledDataSource ds = new ComboPooledDataSource(); //使用默认配置即c3p0-config.xml中<default-config>
// ComboPooledDataSource ds = new ComboPooledDataSource("xx"); //使用命名配置即c3p0-config.xml中<named-config name="xx"> //备胎项目用。
for (int i = 0; i < 6; i++) { //循环模拟: 有几多个用户
Connection conn = ds.getConnection();
System.out.println(conn); //没有下面close的话,5个连接6个人拿超过2s,会发生timed out报错
if(i == 3){ //i=3打印了下又还给连接池,所以i=3打印了两次。
conn.close(); //验证了确实连接还给连接池
}
}
}
}
5个连接6个人拿是不够的。如下最后@2d…打印了2次并且没有发生timed out连接超时错误。
10.druid连接池:自动ds.set
package com.itheima05.druid;
import com.alibaba.druid.pool.DruidDataSource;
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.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class DruidDemo {
public static void main(String[] args) throws Exception {
// method01();
//如下配置文件的方式
InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
Properties p = new Properties();
p.load(is);
DataSource ds = DruidDataSourceFactory.createDataSource(p); //自动解析.properties文件
String sql = "select * from account";
Connection conn = ds.getConnection(); //获取连接
PreparedStatement pstm = conn.prepareStatement(sql);
ResultSet resultSet = pstm.executeQuery();
while(resultSet.next()){
String name = resultSet.getString("name");
System.out.println(name + "-++");
}
conn.close(); //将连接还给连接池
}
//111111111111111111111111111111111111111111111111111111111111111111111111
private static void method01() throws SQLException {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql:///day05");
ds.setUsername("root");
ds.setPassword("1234");
String sql = "select * from account";
Connection conn = ds.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
ResultSet resultSet = pstm.executeQuery();
while(resultSet.next()){
String name = resultSet.getString("name");
System.out.println(name + "--");
}
conn.close();// 将连接还给连接池
// ds.close(); // 销毁 //实际开发服务器不关,连接池不会销毁
}
}
//druid.properties文件,如下key固定写法
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///day05
username=root
password=1234
11.封装druid连接池工具类:ds = DruidDataSourceFactory
File-New-Project-Java。
package com.itheima.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JdbcUtil {
private static DataSource ds; //下面getConn()也能访问到
static{ //静态代码块只运行一次
try {
Properties p = new Properties();
InputStream is = JdbcUtil.class.getClassLoader().getResourceAsStream("druid.properties");
p.load(is);
ds = DruidDataSourceFactory.createDataSource(p); //DataSource ds = 移到最上面了
} catch (Exception e) {
e.printStackTrace();
}
}
public static DataSource getDs() { //属性私有化后提供对外公开的方法
return ds;
}
public static Connection getConn() throws SQLException {
// Connection connection = ds.getConnection();
// return connection;
return ds.getConnection(); //等同上面两行
}
public static void release(AutoCloseable... ios){
for (AutoCloseable io : ios) {
if(io != null){
try {
io.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
package com.itheima.utils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//测试上面JdbcUtil类
public class TestDemo {
@Test
public void method01() throws SQLException {
String sql = "select * from account";
Connection conn = JdbcUtil.getConn();
PreparedStatement pstm = conn.prepareStatement(sql);
ResultSet resultSet = pstm.executeQuery();
while(resultSet.next()){
String name = resultSet.getString("name");
System.out.println(name);
}
JdbcUtil.release(resultSet,pstm,conn); //注意: conn的close是将连接还给连接池
}
}
如下是数据库中name这一列,不是.properties文件的key。
12.execute/update方法:template =
package com.itheima01.template;
import com.itheima.utils.JdbcUtil;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
/*
* JdbcTemplate:是spring的框架的一部分,spring框架是工具箱。作用: 简化jdbc代码编写
* 使用:1. 数据库的操作:DCMQ
* 2. 核心类: JdbcTemplate。构造方法:JdbcTemplate(DataSource ds)
* 核心方法: A. void execute : 理论上可以执行任意sql,适合执行DDL,因为void无返回值
* B. int update : 适合执行DML
* C. 多种多样 query : 适合执行DQL,返回多种多样
*/
public class TemplateDemo01 {
@Test
public void execute(){
String sql = "create table student(id int primary key auto_increment,name varchar(20),age int)";
DataSource ds = JdbcUtil.getDs(); //拿到连接池
JdbcTemplate template = new JdbcTemplate(ds);
template.execute(sql); //无返回值
System.out.println("执行结束");
}
@Test
public void update01(){
String sql = "insert into student values(null,?,?),(null,?,?)";
JdbcTemplate template = new JdbcTemplate(JdbcUtil.getDs());
/*
* int update(String sql, Object... args)
* Object... args:
* 1. Object原因是参数类型是不确定的 -> Object
* 2. ... 原因是参数个数不确定
* 返回值: 被影响的行数
*/
int count = template.update(sql, "zs", 18, "ls", 19);
System.out.println(count); //2
}
@Test
public void update02(){
String sql = "update student set age = ? where id = ?";
JdbcTemplate template = new JdbcTemplate(JdbcUtil.getDs());
Object[] args = {99,1}; //可变参数本质是数组
int update = template.update(sql, args);
System.out.println(update);
}
}
13.queryForXX方法:Map.Entry < String, Object > ,Map < String, Object > 两个数据类型
一行map是一个对象,query方法用的是多个对象这个。
package com.itheima01.template;
import com.itheima.utils.JdbcUtil;
import org.junit.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
import java.util.Map;
import java.util.Set;
/*
* C. 多种多样 query : 适合执行DQL
* 1. queryForXX : XX表示返回值类型
* a. queryForObject
* b. queryForMap
* c. queryForList
* 2. query(RowMapper 行映射器)
*/
public class TemplateDemo02 {
JdbcTemplate template = new JdbcTemplate(JdbcUtil.getDs());
@Test
public void queryForObject01(){
String sql = "select count(*) from student";
/*
* <T> T queryForObject(String sql, Class<T> requiredType)
* requiredType : 返回值类型 -> Class对象
*
* EmptyResultDataAccessException : 空结果异常
* 查询不到任何数据,会报这个错
*/
Integer count = template.queryForObject(sql, Integer.class);
System.out.println(count);
}
@Test
public void queryForObject02(){
String sql = "select name from student where id = ?";
String s = null;
try {
s = template.queryForObject(sql, String.class,3); //3传入上面?
} catch (DataAccessException e) {
e.printStackTrace();
System.out.println("查询不到任何结果");
}
System.out.println(s);
}
@Test
public void queryForMap(){
String sql = "select * from student where id = ?";
Map<String, Object> map = template.queryForMap(sql, 1);
// System.out.println(map); //{id=1,name=zs,age=99}
Set<Map.Entry<String, Object>> entrySet = map.entrySet();
for (Map.Entry<String, Object> entry : entrySet) { //一条
String key = entry.getKey();
Object value = entry.getValue();
System.out.println(key + "=" + value); //竖着打印id=1 name=zs age=99
}
}
@Test
public void queryForList(){
String sql = "select * from student";
List<Map<String, Object>> list = template.queryForList(sql);
for (Map<String, Object> map : list) {
System.out.println(map); //{id=1,name=zs,age=99} 换行 {id=2,name=ls,age=19}
}
}
}
14.query方法:获取类并反射得到方法,rowMapper作为参数传入query方法
package com.itheima01.template;
/*
* JavaBean三要素(必须要有):
* 1. private属性,属性名和表中字段名是一致的!!! 都是引用类型(因为数据库中空为null,不是0)
* 2. public get set方法
* 3. public 空参构造
*/
public class Student {
private Integer id;
private String name;
private Integer age;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
package com.itheima01.template;
import com.itheima.utils.JdbcUtil;
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class TemplateDemo03 {
JdbcTemplate template = new JdbcTemplate(JdbcUtil.getDs());
@Test
public void query01(){
String sql = "select * from student";
/*
* List<T> query(String sql, RowMapper<T> rm)
* RowMapper : 行映射器 (接口)。方法参数中有接口类型, 那么调用的时候必须传入接口的实现类对象
*/
RowMapper<Student> rowMapper = new RowMapper<Student>() {
/*
* 如下Student mapRow(ResultSet resultSet, int i) 映射行: resultSet转换为Student
* 1. resultSet: 结果集(每行)
* 2. i: 当前的行索引(没什么用)
*/
@Override
public Student mapRow(ResultSet resultSet, int i) throws SQLException {
String name = resultSet.getString("name");
int id = resultSet.getInt("id");
int age = resultSet.getInt("age");
Student s = new Student();
s.setId(id);
s.setName(name);
s.setAge(age);
System.out.println(i);
return s;
}
};
//RowMapper行映射器 像 动态代理中 InvocationHandler,mapRow方法像invoke方法
List<Student> list = template.query(sql, rowMapper);
System.out.println(list); //打印出,list里是student类
//[Student{id=1,name='zs',age=99},Student{id=2,name='ls',age=19}]
}
@Test
public void query02(){
String sql = "select * from student";
/*
BeanPropertyRowMapper: 类
1. RowMapper接口的实现类
2. BeanPropertyRowMapper(xx.class); 返回值的泛型
*/
/*
* BeanPropertyRowMapper (底层反射),思路如下:
* 1. 实现RowMapper接口
* 2. 重写mapRow方法 : 每行ResultSet -> javaBean
* 1. 获取结果集中的数据
* 知道结果集有哪些字段 -> 结果集元数据
* 值 = resultSet.get(字段);
* id值 = id
*
* 2. 设置到javabean中去 (需要传参: Student.class)
* clazz = Student.class //获取类
* Student s = clazz.newInstance(); // javabean规范: 默认调用空参构造
* // Student s = new Student(); //等同于上面两行
*
* //并不知道Student对象有哪些方法,通过反射如下
* setIdMethod = clazz.getMethod("setId",int.class);
*
* //怎么知道Student对象中有setId方法呢?
* //因为javabean规范 : 必有set方法。额外要求:set+名字(必须要和表中的字段名一致)
* setIdMethod.invoke(s,id值);
*/
RowMapper<Student> rowMapper = new BeanPropertyRowMapper<>(Student.class);
List<Student> list = template.query(sql, rowMapper);
System.out.println(list); //打印出同query01()
}
}
15.update/query方法源码和测试封装:resultSet.getInt()
package com.itheima03.source;
import com.itheima.utils.JdbcUtil;
import com.itheima01.template.Student;
import org.junit.Test;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/*
* 代码封装: 1. 先把完整的代码写出来
* 2. 进行抽取(定义方法, 参数, 返回值)
* 扩展性: 1. 不变的内容留在框架里
* 2. 变化的内容变成参数
*/
public class SrcDemo {
@Test
public void updateSrc() throws SQLException { //如下update源码完整代码写出来
String sql = "insert into student values(null,?,?)";
DataSource ds = JdbcUtil.getDs(); //1.连接池
Connection conn = ds.getConnection(); //2.拿到连接
PreparedStatement pstm = conn.prepareStatement(sql); //3.预编译
pstm.setString(1,"ww");
pstm.setString(2,"20");
int count = pstm.executeUpdate(); //4.执行
System.out.println(count);
}
@Test
public void update01() throws SQLException { //测试MyTemplate.java中封装的update()。这里面代码和本文第一章update01()一样。
String sql = "insert into student values(null,?,?)";
MyTemplate template = new MyTemplate(JdbcUtil.getDs());
int count = template.update(sql, "ml", 33);
System.out.println(count);
}
@Test
public void update02() throws SQLException { //测试MyTemplate.java中封装的update()
String sql = "insert into student values(null,?,?)";
MyTemplate template = new MyTemplate(JdbcUtil.getDs());
int count = template.update(sql); //没传参,不合法,所以需要框架封装者做健壮性校验
System.out.println(count);
}
//111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
@Test
public void query01() throws SQLException { //如下完整代码写出来,query源码
String sql = "select * from student where id > ?";
DataSource ds = JdbcUtil.getDs();
Connection conn = ds.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setInt(1,2); //1指第一个?,2对?赋值
ResultSet resultSet = pstm.executeQuery();
// resultSet -> List<Student>
List<Student> list = new ArrayList<>();
while(resultSet.next()){
int id = resultSet.getInt("id");
int age = resultSet.getInt("age");
String name = resultSet.getString("name");
Student s = new Student();
s.setId(id);
s.setName(name);
s.setAge(age);
list.add(s);
}
System.out.println(list);
//[Student{id=3,name='ww',age=20},Student{id=4,name='ml',age=33}]
}
@Test
public void query02() throws SQLException { //测试MyTemplate.java中封装的query()
String sql = "select * from student where id > ?";
MyTemplate02 template = new MyTemplate02(JdbcUtil.getDs());
MyRowMapper<Student> rm = new MyRowMapper() {
@Override
public Student mapRow(ResultSet resultSet) throws SQLException { //父类方法抛出编译异常,子类才能抛。
int id = resultSet.getInt("id");
int age = resultSet.getInt("age");
String name = resultSet.getString("name");
Student s = new Student();
s.setId(id);
s.setName(name);
s.setAge(age);
return s;
}
};
List<Student> list = template.query(sql, rm, 2);
System.out.println(list);
//打印同上[Student{id=3,name='ww',age=20},Student{id=4,name='ml',age=33}]
}
}
如下右边rm是父类引用,rm.mapRow执行左边子类重写方法。左边resultSet已由右边遍历结束了。
16.update/query方法封装:pstm.setObject,rm = new MyRowMapper(){}
package com.itheima03.source;
import com.itheima.utils.JdbcUtil;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class MyTemplate { //框架设计者即封装源码
DataSource ds; //不同的方法,同一个ds连接池
public MyTemplate(DataSource ds){ //通过构造传入ds
this.ds = ds;
}
/*
* 代码健壮性: 调用此方法传入的参数个数必须和sql的?数量一致
* 1. 参数的个数:args.length
* 2. sql的?的数量:如下a或b都可以
* a. 截取字符串
* b. 元数据 metadata !!!
* brand(元数据) = 香奈儿 (数据)
* 字段(元数据) = 对应的值(数据)
*/
//如下sql设为局部方法,因为不同方法不同sql
public int update(String sql,Object... args) throws SQLException { //封装updateSrc()
// String sql = "insert into student values(null,?,?)"; //这句会变化,用参数传入
// DataSource ds = JdbcUtil.getDs(); //连接池 //不要和JdbcUtil耦合,所以DataSource ds放到成员位置,通过构造传入。
// pstm.setString(1,"ww"); //也不知道sql占位多少,抽出去。换成下面 for (int i = 0; i < args.length; i++) {pstm.setObject(i+1,args[i]);} //都是从1往后排,没有0,(null,?,?)不是数组
Connection conn = ds.getConnection(); //拿到连接
PreparedStatement pstm = conn.prepareStatement(sql); //预编译
//下面5行健壮性校验,不让框架使用者乱用
ParameterMetaData parameterMetaData = pstm.getParameterMetaData(); //获取参数元数据 (name=?,age=?)
int parameterCount = parameterMetaData.getParameterCount(); //获取问好数量
if(args.length != parameterCount){
throw new StupidBirdException("你个傻鸟,参数个数和?个数对应不上~~");
}
for (int i = 0; i < args.length; i++) {
pstm.setObject(i+1,args[i]);
}
int count = pstm.executeUpdate(); //这里要连接数据库,上面健壮性校验预编译时就报错,这里就不用再连数据库
pstm.close();
conn.close(); //连接还给连接池
return count;
}
public void execute(){
}
}
class StupidBirdException extends SQLException{ //自定义异常
public StupidBirdException(String msg){
super(msg);
}
}
如下是query方法的封装。
package com.itheima03.source;
import com.itheima.utils.JdbcUtil;
import com.itheima01.template.Student;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class MyTemplate02 {
DataSource ds;
public MyTemplate02(DataSource ds){
this.ds = ds;
}
public <T>List<T> query(String sql,MyRowMapper<T> rm,Object... args) throws SQLException {
//往参数列表设置接口方式,强迫用户重写抽象方法
// String sql = "select * from student where id > ?"; // 变量 -> 参数
// DataSource ds = JdbcUtil.getDs();
Connection conn = ds.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
// pstm.setInt(1,2);
int parameterCount = pstm.getParameterMetaData().getParameterCount(); //健壮性校验
if(parameterCount != args.length){
throw new IllegalArgumentException("傻鸟~~");
}
for (int i = 0; i < args.length; i++) { //快捷键 args.fori
pstm.setObject(i+1,args[i]);
}
ResultSet resultSet = pstm.executeQuery();
List<T> list = new ArrayList<>();
while(resultSet.next()){ //一次遍历一行,将这一行映射为java bean对象,所以叫mapRow
/*
* 如下代码会动态变化的(sql不同,结果集就会不同),希望由用户自己来封装javaBean对象。
* 1. 之前: 变量动态 -> 参数(全局,局部)
* 2. 现在: 如何将一段代码动态化:一段代码 提取成 方法
* (在java中,方法 不能作为参数,参数只能是基本类型或引用类型[类,接口,枚举,注解],js中可以将方法作为参数)
*
* 如下我可以提供resultSet结果集给你,你要给我数据设置好的javaBean对象
* Student 方法名(resultSet); -> 抽象方法
* 封装没办法写方法体,需要用户自己写
* 所以 方法名mapRow 要放在 接口MyRowMapper 里
* 所以 参数动态化 放方法里,方法动态化 放接口里
*/
/* int id = resultSet.getInt("id");
int age = resultSet.getInt("age");
String name = resultSet.getString("name");
Student s = new Student();
s.setId(id);
s.setName(name);
s.setAge(age);*/
//将如上7行代码设计为方法。
T s = rm.mapRow(resultSet); //给你resultSet,还我T类
list.add(s);
}
pstm.close();
conn.close();
return list;
}
}
interface MyRowMapper<T>{
T mapRow(ResultSet resultSet) throws SQLException;
}