实训笔记6.29
6.29
一、座右铭
我的故事你说,我的文字我落,我值几两你定,我去何方我挑。
二、批量数据操作
代码示例:
package com.sxuek.study;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.swing.JOptionPane;
/**
* JDBC实现批量数据的操作:仅适用于DML类型的SQL场景
* JDBC支持可以先将SQL语句在Java中缓存起来,小推车 addBatch(); 积攒到一定的数量级之后 统一调用executeBatch()方法将积攒
* 的多条SQL语句传输到MySQL中执行得到结果,调用clearBatch()方法将已经执行完成的缓存SQL清空了。
* JDBC默认情况下连接的数据库不支持批次操作的,就算你写了批次操作,底层也是会一条一条执行的。如果你想使用批次操作,那么必须
* 开启批次操作的开关:url后增加一个参数:rewriteBatchedStatements=true
*
* 想往菜单中添加100000条数据
*
* @author 11018
*
*/
public class Demo {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement prepareStatement = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:33006/project?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true",
"root", "123456");
String sql = "insert into menu(menu_name,menu_price) values(?,?)";
prepareStatement = connection.prepareStatement(sql);
long currentTimeMillis = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
prepareStatement.setString(1, "menu"+i);
prepareStatement.setDouble(2, 20.0);
prepareStatement.addBatch();
if(i % 5000 == 0) {
int[] array = prepareStatement.executeBatch();
prepareStatement.clearBatch();
}
}
long time = System.currentTimeMillis();
System.out.println(time-currentTimeMillis);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
String message = e.getMessage();
if (message.contains("Duplicate entry")) {
JOptionPane.showMessageDialog(null, "该菜品已经存在!");
}
} finally {
if (prepareStatement != null) {
try {
prepareStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
三、JavaBean与数据库中的数据表
JavaBean一般是要和数据库中数据表结合使用的。
有一种特殊的JavaBean叫做实体类,实体类是专门和数据库的某张数据表对应的Java对象,用于封装数据表中对应的数据
实体类的类名===数据库的表名
实体类中属性===数据表的字段
实体类的一个对象===数据表中一行数据
实体类一般使用在如下场景:
- 添加/修改数据的场景 会先把零散的数据封装为实体类,然后借助实体类添加数据
- 查询数据的场景 会把查询回来的一行中多个列的数据封装为一个实体类的对象。
四、 JDBC实现事务操作
代码示例:
package com.sxuek.transcation;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.sxuek.util.DBUtils;
/**
* JDBC实现事务操作
* connection 提供三个和事务有关的方法:
* setAutoCommit(boolean);
* commit()
* rollBack()
* try commit catch rollback
* 转账操作
* @author 11018
*
*/
public class Demo {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement statement = null;
try {
connection = DBUtils.getConnection();
connection.setAutoCommit(false);
String sql ="update account set amount=amount-? where username=?";
statement = DBUtils.getStatement(connection, sql);
int i = DBUtils.executeDMLSQL(statement, 100,"dnn");
String sql1 ="update account set amount=amount+? where username=?";
statement = DBUtils.getStatement(connection, sql1);
int i1 = DBUtils.executeDMLSQL(statement, 100,"xjq");
connection.commit();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
if(connection != null) {
try {
connection.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}finally {
DBUtils.closeResources(connection, statement);
}
}
}
五、封装JDBC工具类
代码示例:
package com.sxuek.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* 封装一个JDBC的工具类
* 目前我们操作JDBC,不管是什么样的SQL语句,执行的步骤基本上都是一摸一样的
* 1、加载驱动
* 2、建立连接
* 3、准备SQL语句
* 4、创建小推车
* 5、执行SQL
* 6、处理返回结果
* 7、关闭资源
*
* 工具类主要可以对JDBC做如下的封装:
* 1、获取连接的方法封装起来
* 2、创建小推车的方法
* 3、执行DML类型的SQL语句以及返回结果
* 4、执行DQL类型的SQL语句以及返回结果----最为复杂的--泛型、反射
* 5、封装两个关闭资源的重载方法
* 6、创建一个配置文件xxx.properties,存放数据库的相关连接信息
* @author 11018
*
*/
public class DBUtils {
private static String DRIVER;
private static String URL;
private static String USERNAME;
private static String PASSWORD;
private DBUtils() {
}
/**
* 1、加载驱动
* 2、读取配置文件,把配置文件中的信息缓存起来 留备后期的静态方法调用
*/
static {
Properties prop = new Properties();
try {
prop.load(new FileInputStream(new File("jdbc.properties")));
DRIVER = prop.getProperty("DRIVER");
URL = prop.getProperty("URL");
USERNAME = prop.getProperty("USERNAME");
PASSWORD = prop.getProperty("PASSWORD");
Class.forName(DRIVER);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 1、封装获取数据库连接的方法
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(URL,USERNAME,PASSWORD);
return connection;
}
/**
* 2、封装小推车的获取方法
* @param connection
* @param sql
* @return
* @throws SQLException
*/
public static PreparedStatement getStatement(Connection connection,String sql) throws SQLException {
PreparedStatement prepareStatement = connection.prepareStatement(sql);
return prepareStatement;
}
/**
* 3、封装一个用来执行DML类型的SQL方法
* SQL语句因为使用预编译的小推车执行的,SQL语句中可能有占位符,执行SQL之前,需要传递数据把占位符给替换成我们需要的数据
*
* @param params 参数就是用户需要给SQL的占位符替换的数据,参数有几个就证明你有几个占位符
* @throws SQLException
*/
public static int executeDMLSQL(PreparedStatement preparedStatement,Object... params) throws SQLException {
/**
* 先根据用户输入的params的个数把SQL语句的占位符替换掉
*/
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i+1, params[i]);
}
int executeUpdate = preparedStatement.executeUpdate();
return executeUpdate;
}
/**
* 4、封装DML类型的SQL--查询单条数据的查询语句
* 一个只查询一条结果
* 查询多条结果
* 一般情况查询成功之后,需要把查询回来的数据封装为一个实体类的对象。
* 方法的返回值应该是一个实体类的对象--泛型
* @param clz 就是我们最后查询成功之后需要使用那个Java类来封装查询结果,Java类的运行时类
* @throws SQLException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static <T> T executeOneResultDQLSQL(PreparedStatement preparedStatement,Class<T> clz,Object... params) throws SQLException, InstantiationException, IllegalAccessException {
/**
* 先根据用户输入的params的个数把SQL语句的占位符替换掉
*/
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i+1, params[i]);
}
ResultSet resultSet = preparedStatement.executeQuery();
//先根据反射机制创建这个类的对象,用来封装数据的 调用的无参构造器
T t = clz.newInstance();
/**
* 将结果集中的每一列数据封装到t对象对应的属性上
* 数据表的字段名=t类中的属性名 字段名是下划线命名法 属性是小驼峰命名法
*/
if(resultSet.next()) {
//获取t对象中所有属性,然后将属性转换下划线的命名格式,然后在根据字段名在rs获取对应列的值,然后将值设置到t对象的对应属性中
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//获取的小驼峰的属性名
String name = field.getName();
//小驼峰命名法转下划线命名法
char[] charArray = name.toCharArray();
StringBuilder sb = new StringBuilder();
for (char ch : charArray) {
if(ch >= 65 && ch <=90) {
ch = (char)(ch+32);
sb.append("_");
sb.append(ch);
}else {
sb.append(ch);
}
}
Object value = resultSet.getObject(sb.toString());
field.set(t, value);
}
}
resultSet.close();
return t;
}
/**
* 5、查询多条DQL的返回结果
* @param <T>
* @param preparedStatement
* @param clz
* @param params
* @return
* @throws SQLException
* @throws InstantiationException
* @throws IllegalAccessException
*/
public static <T> List<T> executeMoreResultDQLSQL(PreparedStatement preparedStatement,Class<T> clz,Object... params) throws SQLException, InstantiationException, IllegalAccessException {
/**
* 先根据用户输入的params的个数把SQL语句的占位符替换掉
*/
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i+1, params[i]);
}
ResultSet resultSet = preparedStatement.executeQuery();
//先根据反射机制创建这个类的对象,用来封装数据的 调用的无参构造器
List<T> list = new ArrayList<>();
/**
* 将结果集中的每一列数据封装到t对象对应的属性上
* 数据表的字段名=t类中的属性名 字段名是下划线命名法 属性是小驼峰命名法
*/
while(resultSet.next()) {
T t = clz.newInstance();
//获取t对象中所有属性,然后将属性转换下划线的命名格式,然后在根据字段名在rs获取对应列的值,然后将值设置到t对象的对应属性中
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//获取的小驼峰的属性名
String name = field.getName();
//小驼峰命名法转下划线命名法
char[] charArray = name.toCharArray();
StringBuilder sb = new StringBuilder();
for (char ch : charArray) {
if(ch >= 65 && ch <=90) {
ch = (char)(ch+32);
sb.append("_");
sb.append(ch);
}else {
sb.append(ch);
}
}
Object value = resultSet.getObject(sb.toString());
field.set(t, value);
}
//遍历得到的一条查询结果添加到集合当中
list.add(t);
}
resultSet.close();
return list;
}
/**
* 6、关闭资源
*/
public static void closeResources(Connection conn,PreparedStatement preparedStatement) {
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
测试类:
package com.sxuek.test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import com.sxuek.review.Menu;
import com.sxuek.util.DBUtils;
public class Test01 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement statement = null;
try {
connection = DBUtils.getConnection();
String sql = "select * from menu";
statement = DBUtils.getStatement(connection, sql);
//Menu menu = DBUtils.executeOneResultDQLSQL(statement, Menu.class, 1);
List<Menu> list = DBUtils.executeMoreResultDQLSQL(statement, Menu.class);
System.out.println(list);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
DBUtils.closeResources(connection, statement);
}
}
}
配置文件:jdbc
DRIVER=com.mysql.cj.jdbc.Driver
URL=jdbc:mysql://localhost:33006/project?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
USERNAME=root
PASSWORD=123456
六、数据库连接池
数据库当中也存在一个池子的概念—数据库连接池
数据库连接池是由JDBC提供的一种思想,在JDBC操作数据库的时候,和数据库建立的连接是非常宝贵的资源,而且每一次连接的建立都得需要耗费差不多0.05~1s左右的时间
我们能不能把连接缓存起来,不用的时候放到一个地方,用的时候从那个地方获取回来一个连接使用。
JDBC提供了一个Java接口DataSource
创建数据库连接池:
1、数据库连接池的容量
2、多个数据库连接用什么东西存储—LinkedList
3、如何从数据库连接池获取一个连接
4、如果将连接放回数据库连接当中 close()
6.1 连接池
代码示例:
package com.sxuek.pool;
/**
* JDBC常用的数据库连接池的使用:
* C3P0数据库连接池
* druid数据库连接池
* dbcp数据库连接池
* @author 11018
*
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class Demo02 {
@Test
public void c3p0test() throws SQLException {
/**
* C3P0连接池的实现类CombopooledDataSource
* 需要两个依赖jar包
* 需要创建一个配置文件 c3p0-config.xml文件 文件必须放到src目录下
*/
DataSource dataSource = new ComboPooledDataSource("myDataSource");
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
@Test
public void dbcptest() throws Exception, IOException {
Properties prop = new Properties();
prop.load(new FileInputStream(new File("dbcp.properties")));
DataSource dataSource = BasicDataSourceFactory.createDataSource(prop);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
@Test
public void druidtest() throws Exception, IOException {
Properties prop = new Properties();
prop.load(new FileInputStream(new File("druid.properties")));
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
}
测试类:
package com.sxuek.pool;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
public class Test01 {
public static void main(String[] args) {
try {
DataSource dataSource = new MyDataSource(2);
Connection connection = dataSource.getConnection();
System.out.println(connection);//com.mysql.cj.jdbc.ConnectionImpl@2aece37d
Connection connection1 = dataSource.getConnection();
System.out.println(connection1);//com.mysql.cj.jdbc.ConnectionImpl@548a102f
Connection connection2 = dataSource.getConnection();
System.out.println(connection2);//数据库连接池无可用连接,请稍后重试
connection.close();
Connection connection3 = dataSource.getConnection();
System.out.println(connection3);//com.mysql.cj.jdbc.ConnectionImpl@2aece37d
connection1.close();
Connection connection4 = dataSource.getConnection();
System.out.println(connection4);//com.mysql.cj.jdbc.ConnectionImpl@548a102f
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
配置文件:
-
c3p0
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <named-config name="myDataSource"> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:33006/myemployees?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true</property> <property name="user">root</property> <property name="password">123456</property> <property name="acquireIncrement">10</property> <property name="initialPoolSize">1</property> <property name="minPoolSize">1</property> <property name="maxPoolSize">100</property> <!-- intergalactoApp adopts a different approach to configuring statement caching --> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>
-
dbcp
driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:33006/myemployees?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true username=root password=123456
-
druid
driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:33006/myemployees?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true username=root password=123456
6.2 动态代理模式
代码示例:
package com.sxuek.pool;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;
import javax.sql.DataSource;
import com.sxuek.util.DBUtils;
/**
* 自定义数据库连接池
* @author 11018
*
*/
public class MyDataSource implements DataSource{
//1、存储数据库连接使用的容器--连接池
private LinkedList<Connection> pools =new LinkedList<>();
public MyDataSource(int capacity) throws SQLException {
if(capacity <=0 && capacity >=100) {
System.out.println("容量不合法!");
}else {
for (int i = 0; i < capacity; i++) {
//获取数据库连接--不能用,连接的close方法默认是释放资源的 而非将连接放回到数据库连接池
//被代理对象 需要代理对象把被代理对象的close方法重写了
Connection connection = DBUtils.getConnection();
/**
* Java动态代理模式产生动态代理对象需要传递三个参数:
* 1、ClassLoader 类加载器
* 2、代理对象和被代理对象它两的共同实现的接口
* 3、InvocationHandler:调用方法时候,代理对象如何调用被代理对象的方法
*/
Connection proxyInstance = (Connection) Proxy.newProxyInstance(connection.getClass().getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() {
/**
* proxy 代理对象
* method 调用的代理对象的方法
* args 方法的参数
* 返回值 方法执行完成的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 调用代理对象的方法如何最后执行被代理对象的方法
* 出来close
*/
String name = method.getName();
if (name.equals("close")) {
//连接放回数据库连接池
pools.addLast((Connection)proxy);
}else {
Object invoke = method.invoke(connection, args);
return invoke;
}
return null;
}
});
pools.addLast(proxyInstance);
}
}
}
@Override
public PrintWriter getLogWriter() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public int getLoginTimeout() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO Auto-generated method stub
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public Connection getConnection() throws SQLException {
if(pools.size()>0) {
Connection connection = pools.removeFirst();
return connection;
}else {
System.out.println("数据库连接池无可用连接,请稍后重试");
return null;
}
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
// TODO Auto-generated method stub
return null;
}
}
七、单元测试
【注意】:包下不能有与test重名的类
代码示例:
package com.sxuek.pool;
import org.junit.Before;
import org.junit.*;
/**
* Java的单元测试工具Junit
* 帮助我们进行代码测试的,通过Junit实现在一个类当中将普通的Java方法当作main函数来使用
* 1、需要引入单元测试的依赖
* 2、主要使用到了三个注解--都是只能声明在方法上
* @Before 一个Java类中可以存在一个 等同于Java中的代码块,会自动执行
* 当@Test方法执行的时候会先执行@Before修饰的方法
*
* @Test 一个Java类中可以存在多个
* @Test修饰的方法可以当作普通的main函数来使用
* @After 一个Java类中可以存在一个
* 当@Test方法执行结束会执行@After里面的方法
*
* @author 11018
*
*/
public class Demo01 {
@Before
public void before() {
System.out.println("before的方法执行了");
}
@Test
public void test01() {
System.out.println("test01测试方法执行了");
}
@Test
public void test02() {
System.out.println("test02测试方法执行了");
}
@After
public void destory() {
System.out.println("destory方法执行了");
}
}