5. 批量插入
/*使用PreparedStatement实现批量数据的操作*
* CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(25)
);
* 向goods表中插入1w条数据
*/
public class InsertTest {
//批量添加方式1
@Test
public void test1() throws Exception {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into goods(name) values(?)";
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 1; i <= 10000; i++) {
ps.setObject(1, "name_" + i);
//“攒”sql
ps.addBatch();
if (i % 500 == 0) {
//执行batch
ps.executeBatch();
//清空batch
ps.clearBatch();
}
}
JDBCUtils.closetResource(connection, ps);
}
//批量插入方式2,优化1
@Test
public void test2() throws Exception {
Connection connection = JDBCUtils.getConnection();
connection.setAutoCommit(false);
String sql = "insert into goods(name) values(?)";
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 1; i <= 10000; i++) {
ps.setObject(1, "name_" + i);
//“攒”sql
ps.addBatch();
if (i % 500 == 0) {
//执行batch
ps.executeBatch();
//清空batch
ps.clearBatch();
}
}
connection.commit();
JDBCUtils.closetResource(connection, ps);
}
}
PreparedStatement VS Statement
PreparedStatement是Statement的子接口,表示一条预编译过的SQL语句
PreparedStatement最大可能提升性能,对预编译语句提供性能优化,语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会被执行
PreparedStatement可以防止SQL注入问题,还可以对Blob字段进行操作
6. 数据库事务
6.1 JDBC事务处理
- 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态
- 事务处理原则:保证所有事务都作为一个工作单元来执行,当在一个事务中执行多个操作时,要么u松油的事务都被提交(commit),要么放弃所有修改,整个事务回滚(rollback)到最初状态
public class TransactionTest {
/*
* AA用户给BB转账100
* */
@Test
public void test1() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
//取消数据的自动取消
conn.setAutoCommit(false);
// System.out.println(conn.getAutoCommit());
String sql1 = "update user_table set balance = balance -100 where user=?";
update2(conn, sql1, "AA");
//模拟异常
System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance +100 where user=?";
update2(conn, sql2, "BB");
System.out.println("转账成功");
//提交数据
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚数据
try {
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
} finally {
JDBCUtils.closetResource(conn, null);
}
}
//通用的增删改操作--version1.0
public int update(String sql, Object... args) {//sql中占位符的个数与可变形参的长度相同
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();
//2.预编译sql语句,返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);//!!!!
}
//4.执行
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.关闭资源
JDBCUtils.closetResource(conn, ps);
}
return 0;
}
//考虑事务后的操作--version2.0
public int update2(Connection conn, String sql, Object... args) {
PreparedStatement ps = null;
try {
//2.预编译sql语句,返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);//!!!!
}
//4.执行
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//主要针对于数据库连接池时修改其为自动提交数据
try {
conn.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//5.关闭资源
JDBCUtils.closetResource(null, ps);
}
return 0;
}
}
6.2 事务的ACID属性
- 原子性(Atomicity)是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
- 一致性(Consistency)必须使数据库从一个一致性状态变换到另外一个一致性状态
- 隔离性(Isolation)是指一个事务的执行不被其他事务干扰,一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
- 持久性(Durability)指一个事务一旦被提交,对数据库中数据的改变是永久的,接下来的其他操作不应该对其有任何影响
6.2.1 数据库并发问题
- 脏读:对于两个事务T1,T2,T1读取了已经被T2更新但还没有被提交的字段。之后,若T2回滚,T1读取的内容就是临时且无效的
- 不可重复读:T1读取了一个字段,然后T2更新了该字段。之后,T1再次读取同一个字段,值就不同了
- 幻读:T1从一个表中读取了一个字段,然后T2在该表中插入了新的行。之后,如果T1再次读取同一个表,就会多出几行
6.2.2 四种隔离级别
隔离级别 | 描述 |
---|---|
READ UNCOMMITTED(读未提交数据) | 允许事务读取未被其他事务提交的变更,脏读、不可重复读、幻读都会出现 |
READ COMMITED(读已提交数据) | 只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读和幻读仍然出现 |
REPEATABLE READ(可重复读) | 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,幻读依旧存在 |
SERIALIZABLE(串行化) | 确保事务可以从一个表读取相同的行。禁止其他事务对该表执行增删改操作,所有问题都可避免,但性能低下 |
- Oracle默认READ COMMITD,Mysql默认REPEATABLE READ
//事务
@Test
public void test2() throws Exception {
Connection conn = JDBCUtils.getConnection();
//获取当前连接的隔离级别
System.out.println(conn.getTransactionIsolation());
//取消自动提交
conn.setAutoCommit(false);
String sql = "select user,password,balance from user_table where user=?";
List<User> users = getInstance(conn, User.class, sql, "CC");
users.forEach(System.out::println);
}
@Test
public void test3() throws Exception {
Connection conn = JDBCUtils.getConnection();
//取消自动提交
conn.setAutoCommit(false);
String sql = "update user_table set balance =? where user=?";
update2(conn, sql, 8888, "CC");
Thread.sleep(15000);
System.out.println("修改结束");
}
//通用的查询操作,用于返回数据表中的记录--考虑事务version2.0
public <T> List<T> getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
//创建集合对象
ArrayList<T> list = new ArrayList<>();
while (rs.next()) {
T t = clazz.newInstance();
//处理一行结果集中的每个列
for (int i = 0; i < columnCount; i++) {
//获取列值
Object columnValue = rs.getObject(i + 1);
//获取每个列的列名
String columnName = rsmd.getColumnName(i + 1);
//给cust对象指定的columnName属性,赋值为columnValue,通过反射
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closetResource(null, ps, rs);
}
return null;
}
7. DAO及其实现类
7.1 BaseDAO
//封装了针对于数据表的通用的操作
//DAO(databse access object)
public abstract class BaseDAO {
//考虑事务后的操作--version2.0
public int update2(Connection conn, String sql, Object... args) {
PreparedStatement ps = null;
try {
//2.预编译sql语句,返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);//!!!!
}
//4.执行
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//主要针对于数据库连接池时修改其为自动提交数据
try {
conn.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//5.关闭资源
JDBCUtils.closetResource(null, ps);
}
return 0;
}
//通用的查询操作,用于返回数据表中的记录--考虑事务version2.0
public <T> List<T> getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
//创建集合对象
ArrayList<T> list = new ArrayList<>();
while (rs.next()) {
T t = clazz.newInstance();
//处理一行结果集中的每个列
for (int i = 0; i < columnCount; i++) {
//获取列值
Object columnValue = rs.getObject(i + 1);
//获取每个列的列名
String columnName = rsmd.getColumnName(i + 1);
//给cust对象指定的columnName属性,赋值为columnValue,通过反射
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closetResource(null, ps, rs);
}
return null;
}
//用于查询特殊值的通用方法
public <E> E getValue(Connection con, String sql, Object... args) throws SQLException {
PreparedStatement ps = con.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
ResultSet rs = ps.executeQuery();
if (rs.next()) {
return (E) rs.getObject(1);
}
JDBCUtils.closetResource(null, ps, rs);
return null;
}
}
7.2 CustomerDAO
//此接口用于规范针对于customers表的常用操作
public interface CustomerDAO {
//将cust对象添加到数据库中
void insert(Connection connection, Customer cust);
//根据指定的id,删除表中的一条记录
void deleteById(Connection connection, int id);
//针对内存中的cust对象修改数据表中的记录
void updateById(Connection connection, Customer customer);
//指定id查询得到对应的Customer对象
Customer getCustomerById(Connection connection, int id);
//查询表中所有记录构成的集合
List<Customer> getAll(Connection connection);
//返回数据表中的数据的条目数
Long getCount(Connection connection);
//返回数据表中最大的生日
Date getMaxBirth(Connection connection);
}
7.3 CustomerDAOImpl
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO {
@Override
public void insert(Connection connection, Customer cust) {
String sql = "insert into customers(name,email,birth)values(?,?,?)";
update2(connection, sql, cust.getName(), cust.getEmail(), cust.getBirth());
}
@Override
public void deleteById(Connection connection, int id) {
String sql = "delete from customers where id=?";
update2(connection, sql, id);
}
@Override
public void updateById(Connection connection, Customer customer) {
String sql = "update customers set name=?,email=?,birth =? where id = ?";
update2(connection, sql, customer.getName(), customer.getEmail(), customer.getBirth(), customer.getId());
}
@Override
public Customer getCustomerById(Connection connection, int id) {
return null;
}
@Override
public List<Customer> getAll(Connection connection) {
String sql = "select id,name,email,birth from customers";
List<Customer> list = getInstance(connection, Customer.class, sql);
return list;
}
@Override
public Long getCount(Connection connection) {
String sql = "select count(*) from customers";
return getValue(connection, sql);
}
@Override
public Date getMaxBirth(Connection connection) {
String sql="select max(birth) from customers";
return getValue(connection,sql);
}
}
7.4 CustomerDAOImpl
public class CustomerDAOImplTest {
CustomerDAOImpl dao = new CustomerDAOImpl();
@Test
public void testInsert() {
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
Customer customer = new Customer(1, "zhangsan", "zhangs@zs.com", new Date(46474874465L));
dao.insert(connection, customer);
System.out.println("添加成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closetResource(connection, null);
}
}
@Test
public void testDeleteById() {
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
dao.deleteById(connection, 23);
System.out.println("删除成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closetResource(connection, null);
}
}
@Test
public void testUpdateCustomer() {
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
Customer customer = new Customer(22, "zhangsan", "zhangs@zs.com", new Date(46474874465L));
dao.updateById(connection, customer);
System.out.println("修改成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closetResource(connection, null);
}
}
@Test
public void testGetAll() {
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
List<Customer> list = dao.getAll(connection);
list.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closetResource(connection, null);
}
}
@Test
public void testGetCount() {
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
System.out.println(dao.getCount(connection));
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closetResource(connection, null);
}
}
@Test
public void testMaxBirth() {
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
System.out.println(dao.getMaxBirth(connection));
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closetResource(connection, null);
}
}
}
8. 数据库连接池
8.1数据库连接池技术
- 为数据库连接建立了一个“缓冲池”,预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,从“缓冲池”中取出一个,使用完毕后再放回去
- 数据库连接池负责分配、管理和释放数据库连接,他允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
8.2 多种开源数据库连接池
- JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由服务器(Weblogic,WebSphere,Tomcat)提供实现
- DBCP是Apache提供的数据库连接池,tomcat服务器自带dbcp数据库连接池,速度较C3P0快,但存在BUG,Hibernate3已不再支持
- C3P0速度较慢,稳定还可以,hibernate官方推荐使用
- Druid是阿里提供的,记说是集优点于一身的
- DatSource通常被称为数据源,他包含连接池和连接池管理两个部分,习惯上把DataSource称为连接池
8.2.1 C3P0
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- This app is massive! -->
<named-config name="helloc3p0">
<!--提供获取连接的4个基本信息 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 进行数据库连接池管理的基本信息-->
<!--当数据库连接池中的连接数不够时,C3P0一次性向数据库服务器申请的连接数-->
<property name="acquireIncrement">5</property>
<!--C3P0数据库连接池中初始化时的连接数-->
<property name="initialPoolSize">10</property>
<!--维护的最少连接数-->
<property name="minPoolSize">10</property>
<!--维护的最多的连接数-->
<property name="maxPoolSize">100</property>
<!--维护最多的Statement个数-->
<property name="maxStatements">50</property>
<!--每个连接可以最多使用Statement的个数-->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
public class C3P0Test {
//方式一
@Test
public void testGetConnection() throws Exception {
//获取C3P0数据库连接池
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver"); //loads the jdbc driver
cpds.setJdbcUrl("jdbc:mysql://localhost/test");
cpds.setUser("root");
cpds.setPassword("123456");
//通过设置相关的参数,对数据库连接池进行管理
//设置初始时数据库连接池中的连接数
cpds.setInitialPoolSize(10);
Connection connection = cpds.getConnection();
System.out.println(connection);
//销毁C3P0数据库连接池,一般不需要
DataSources.destroy(cpds);
}
//方式二,使用配置文件
@Test
public void testConnection() throws SQLException {
ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");
Connection connection = cpds.getConnection();
System.out.println(connection);
}
}
8.2.2 DBCP
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost/test
username=root
password=123456
initialSize=10
public class DBCPTest {
//测试DBCP的数据库连接池技术
//方式一
@Test
public void testGetConnection() throws SQLException {
//创建DBCP的数据库连接池
BasicDataSource source = new BasicDataSource();
//设置基本信息
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost/test");
source.setUsername("root");
source.setPassword("gaiwei200888");
source.setInitialSize(10);
source.setMaxActive(10);
Connection connection = source.getConnection();
System.out.println(connection);
}
//方式二。使用配置文件
@Test
public void test2() throws Exception {
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
pros.load(is);
DataSource dataSource = BasicDataSourceFactory.createDataSource(pros);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
}
8.2.3 Druid(德鲁伊)
url=jdbc:mysql:///test
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver
initialSize=8
public class DruidTest {
@Test
public void getConnection() throws Exception {
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);
DataSource source = DruidDataSourceFactory.createDataSource(pros);
Connection connection = source.getConnection();
System.out.println(connection);
}
}
public class JDBCUtil {
//数据库连接池只i需要提供一个即可
private static ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");
private static DataSource source;
static {
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
try {
pros.load(is);
source = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}
}
//使用C3P0数据库连接池技术
public static Connection getConnection1() throws SQLException {
Connection connection = cpds.getConnection();
return connection;
}
public static Connection getConnection2() throws Exception {
//创建一个DBCP数据库连接池
Connection connection = source.getConnection();
return connection;
}
private static DataSource dataSource;
static {
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
try {
pros.load(is);
dataSource = DruidDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection3() throws SQLException {
Connection connection = dataSource.getConnection();
return connection;
}
}
9. Apache-DBUtils实现CRUD操作
- commons-dbutils是Apache提供的一个开源JDBC工具类库,是对JDBC的简单封装,使用dbutils极大简化jdbc编码的工作量
- API介绍org.apache.commons.dbutils.QueryRunner
- org.apache.commons.dbutils.ResultSertHandler
- 工具类org.apache.commons.dbutils.DbUtils
9.1 主要API的使用
public class QueryRunnerTest {
//插入
@Test
public void testInert() throws SQLException {
QueryRunner runner = new QueryRunner();
Connection connection = JDBCUtil.getConnection3();
String sql = "insert into customers (name,email,birth) values(?,?,?)";
int inertCount = runner.update(connection, sql, "lilili", "lili@li.com", "1997-02-06");
System.out.println(inertCount);
}
//查询,BeanHandler是ResultSetHandler接口的实现类,用于封装表中的一条记录
@Test
public void testQuery1() throws SQLException {
QueryRunner runner = new QueryRunner();
Connection connection = JDBCUtil.getConnection3();
String sql = "select id,name,email,birth from customers where id =? ";
BeanHandler<Customer> handler = new BeanHandler<Customer>(Customer.class);
Customer customer = runner.query(connection, sql, handler, 13);
System.out.println(customer);
}
@Test
public void testQuery2() throws SQLException {
QueryRunner runner = new QueryRunner();
Connection connection = JDBCUtil.getConnection3();
String sql = "select id,name,email,birth from customers where id <? ";
BeanListHandler<Customer> handler = new BeanListHandler<Customer>(Customer.class);
List<Customer> customers = runner.query(connection, sql, handler, 13);
customers.forEach(System.out::println);
}
@Test
public void testQuery3() throws SQLException {
QueryRunner runner = new QueryRunner();
Connection connection = JDBCUtil.getConnection3();
String sql = "select id,name,email,birth from customers where id =? ";
MapHandler handler = new MapHandler();
Map<String, Object> map = runner.query(connection, sql, handler, 13);
System.out.println(map);
}
@Test
public void test4() throws SQLException {
QueryRunner runner = new QueryRunner();
Connection connection = JDBCUtil.getConnection3();
String sql = "select count(*) from customers";
ScalarHandler h = new ScalarHandler();
Long count = (Long) runner.query(connection, sql, h);
System.out.println(count);
}
//自定义ResultSetHandler的类
@Test
public void test5() throws SQLException {
QueryRunner runner = new QueryRunner();
Connection connection = JDBCUtil.getConnection3();
String sql = "select id,name,email,birth from customers where id =? ";
ResultSetHandler<Customer> handler = new ResultSetHandler<>() {
@Override
public Customer handle(ResultSet resultSet) throws SQLException {
if (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String email = resultSet.getString("email");
Date birth = resultSet.getDate("birth");
Customer customer = new Customer(id, name, email, birth);
return customer;
}
return null;
}
};
Customer customer = runner.query(connection, sql, handler, 10);
System.out.println(customer);
}
}