jdbc由java语言的规范(接口,存储在java.sql和javax.sql包中的api)和各个数据库厂商的实现驱动(jar)组成。
/**
* 查询全部数据!
* 将数据存到List
@Test
public void testQueryMap() throws Exception{
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
//TODO: 切记, ? 只能代替 值!!!!! 不能代替关键字 特殊符号 容器名
String sql = "select id,account,password,nickname from t_user ;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//占位符赋值 本次没有占位符,省略
//发送查询语句
ResultSet resultSet = preparedStatement.executeQuery();
//创建一个集合
List<Map> mapList = new ArrayList<>();
//获取列信息对象
//metaData装的是当前列的信息对象(通过他可以获取列对应的下角标,或者是列的数量)
ResultSetMetaData metaData = resultSet.getMetaData();
//获取列的数量
int columnCount = metaData.getColumnCount();
while (resultSet.next()) {
//一行数据对应一个map
Map map = new HashMap();
for (int i = 1; i <= columnCount; i++) {
//笨蛋写法:
//map.put("id",resultSet.getInt("id"));好几行,写法固定
//value:获取指定下角标的值 key;用metaData
//getColumnLabel先获取列的别名,没有别名用列名,而getColumnName只是列名
map.put(metaData.getColumnLabel(i), resultSet.getObject(i));
}
mapList.add(map);
}
System.out.println(mapList);
//关闭资源close
preparedStatement.close();
connection.close();
resultSet.close();
}
/**
* 返回插入的主键!
* 主键:数据库帮助维护的自增长的整数主键!
* @throws Exception
*/
@Test
public void returnPrimaryKey() throws Exception{
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=root");
//3.编写SQL语句结构
String sql = "insert into t_user (account,password,nickname) values (?,?,?);";
//4.创建预编译的statement,传入SQL语句结构
/**
* TODO: 第二个参数填入 1 | Statement.RETURN_GENERATED_KEYS
* 告诉statement携带回数据库生成的主键!
*/
PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//5.占位符赋值
statement.setObject(1,"towgog");
statement.setObject(2,"123456");
statement.setObject(3,"二狗子");
//6.执行SQL语句 【注意:不需要传入SQL语句】 DML
int i = statement.executeUpdate();
//7.结果集解析
System.out.println("i = " + i);
//一行一列的数据!里面就装主键值!固定用getGeneratedKeys
ResultSet resultSet = statement.getGeneratedKeys();
resultSet.next();
int anInt = resultSet.getInt(1);//指向第一列
System.out.println("anInt = " + anInt);
//8.释放资源
statement.close();
connection.close();
}
rewriteBatchedStatements=true
/**
*改动了三处:(1)路径(2)必写values,且后面不加;(3)装货addBatch()最后executeBatch();
* 批量细节:
* 1.url?rewriteBatchedStatements=true
* 2.insert 语句必须使用 values
* 3.语句后面不能添加分号;
* 4.语句不能直接执行,每次需要装货 addBatch() 最后 executeBatch();
*
* 批量插入优化!
* @throws Exception
*/
@Test
public void batchInsertYH() throws Exception{
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true",
"root","root");
//3.编写SQL语句结构
String sql = "insert into t_user (account,password,nickname) values (?,?,?)";
//4.创建预编译的statement,传入SQL语句结构
/**
* TODO: 第二个参数填入 1 | Statement.RETURN_GENERATED_KEYS
* 告诉statement携带回数据库生成的主键!
*/
long start = System.currentTimeMillis();
PreparedStatement statement = connection.prepareStatement(sql);
for (int i = 0; i < 10000; i++) {
//5.占位符赋值
statement.setObject(1,"ergouzi"+i);
statement.setObject(2,"lvdandan");
statement.setObject(3,"驴蛋蛋"+i);
//6.装车
statement.addBatch();
}
//发车! 批量操作!
statement.executeBatch();
long end = System.currentTimeMillis();
System.out.println("消耗时间:"+(end - start));
//7.结果集解析
//8.释放资源
connection.close();
}
事务类型:
自动提交步骤:
connection.setAutoCommit(false);
关闭自动提交// 呼应jdbc技术
try{
connection.setAutoCommit(false); //关闭自动提交。相当于执行SET autocommit = off
//注意,只要当前connection对象,进行数据库操作,都不会自动提交事务
//数据库动作!
//statement - 单一的数据库动作 c u r d
//connection - 操作事务
connection.commit();
}catch(Execption e){
connection.rollback();
}
数据库连接池用于管理及复用数据库连接。
JDBC 的数据库连接池使用 javax.sql.DataSource
接口进行规范,所有的第三方连接池都实现此接口。也就是说,所有连接池获取连接的和回收连接方法都一样,不同的只有性能和扩展功能。
产品比对:
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql://localhost:3306/database
/**
* 不直接在java代码编写配置文件
* 利用工厂模式,传入配置文件对象,创建连接池
*/
@Test
public void druidSoft() throws Exception {
Properties properties = new Properties();
InputStream ips = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(ips);
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
// JDBC的步骤 正常curd
// 连接池连接的close方法是回收连接,而不是关闭连接
connection.close();
}
配置 | 缺省 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) | |
jdbcUrl | 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter | |
driverClassName | 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) | |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | |
numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 | |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
proxyFilters | 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
利用ThreadLocal完成线程级Connection重用
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
//使用事务时,Service和dao属于同一线程,不用再传参数了
/*
这个工具类的作用就是用来给所有的SQL操作提供“连接”,和释放连接。
这里使用ThreadLocal的目的是为了让同一个线程,在多个地方getConnection得到的是同一个连接。
这里使用DataSource的目的是为了(1)限制服务器的连接的上限(2)连接的重用性等
*/
public class JDBCTools {
private static DataSource ds;
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
static{//静态代码块,JDBCToolsVersion1类初始化执行
try {
Properties pro = new Properties();
pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
Connection connection = tl.get();
if(connection == null){//当前线程还没有拿过连接,就给它从数据库连接池拿一个
connection = ds.getConnection();
tl.set(connection);
}
return connection;
}
public static void free() throws SQLException {
Connection connection = tl.get();
if(connection != null){
tl.remove();
connection.setAutoCommit(true);//避免还给数据库连接池的连接不是自动提交模式(建议)
connection.close();
}
}
}
基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,我们称为BaseDao
针对DQL查询和非DQL进行,分成两类
public abstract class BaseDao {
/*
通用的增、删、改的方法
String sql:sql
Object... args:给sql中的?设置的值列表,可以是0~n
*/
protected int update(String sql,Object... args) throws SQLException {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//设置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
//执行sql
int len = ps.executeUpdate();
ps.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return len;
}
/*
通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
这里的clazz接收的是T类型的Class对象,
如果查询员工信息,clazz代表Employee.class,
如果查询部门信息,clazz代表Department.class,
返回List list
*/
protected <T> ArrayList<T> query(Class<T> clazz,String sql, Object... args) throws Exception {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//设置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
ArrayList<T> list = new ArrayList<>();
ResultSet res = ps.executeQuery();
/*
获取结果集的元数据对象。
元数据对象中有该结果集一共有几列、列名称是什么等信息
*/
ResultSetMetaData metaData = res.getMetaData();
int columnCount = metaData.getColumnCount();//获取结果集列数
//遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。
while(res.next()){
//循环一次代表有一行,代表有一个T对象
T t = clazz.newInstance();//要求这个类型必须有公共的无参构造
//把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
for(int i=1; i<=columnCount; i++){
//for循环一次,代表取某一行的1个单元格的值
Object value = res.getObject(i);
//这个值应该是t对象的某个属性值
//获取该属性对应的Field对象
// String columnName = metaData.getColumnName(i);//获取第i列的字段名
String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//这么做可以操作private的属性
field.set(t, value);
}
list.add(t);
}
res.close();
ps.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return list;
}
protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
ArrayList<T> list = query(clazz, sql, args);
if(list == null || list.size() == 0){
return null;
}
return list.get(0);
}
}