2.JDBC
2.1.概念
-
Java DataBase Connectivity Java 数据库连接, Java语言操作数据库
-
JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
2.2.使用步骤
- 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
- 复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下
- 右键–>Add As Library
- 注册驱动
- 获取数据库连接对象 Connection
- 定义sql语句
- 获取执行sql语句的对象 Statement
- 执行sql,接受返回结果
- 处理结果
- 释放资源
- Example
//1.导入驱动jar包,复制jar文件到指定工程目录下
//2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "username", "password");
//4.定义sql语句
String sql = "update account set balance = 500 where id = 1";
//5.获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//6.执行sql
int count = stmt.executeUpdate(sql);
//7.处理结果
System.out.println(count);
//8.释放资源
stmt.close();
conn.close();
2.3.对象详解
2.3.1.DriverManager:驱动管理对象
- 注册驱动:告诉程序该使用哪一个数据库驱动jar
- static void registerDriver(Driver driver) :注册与给定的驱动程序 DriverManager 。
- 写代码使用: Class.forName(“com.mysql.jdbc.Driver”);
- 通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
//注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。
- 获取数据库连接:
- 方法:static Connection getConnection(String url, String user, String password)
- 参数:
- url:指定连接的路径
- 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称,例如:jdbc:mysql://localhost:3306/db3
如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
- user:用户名 password:密码
2.3.2.Connection:数据库连接对象
- 获取执行sql 的对象
- Statement createStatement()
- PreparedStatement prepareStatement(String sql)
- 管理事务:
- 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
- 提交事务:commit()
- 回滚事务:rollback()
2.3.3.Statement:执行sql的对象
- 执行sql
- boolean execute(String sql) :可以执行任意的sql 了解
- int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
- 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败。
- ResultSet executeQuery(String sql) :执行DQL(select)语句
- Example:
1. account表 添加一条记录
2. account表 修改记录
3. account表 删除一条记录
Statement stmt = null;
Connection conn = null;
try {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 定义sql
String sql = "insert into account values(null,'王五',3000)";
//3.获取Connection对象
conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root");
//4.获取执行sql的对象 Statement
stmt = conn.createStatement();
//5.执行sql
int count = stmt.executeUpdate(sql);//影响的行数
//6.处理结果
System.out.println(count);
if(count > 0){
System.out.println("添加成功!");
}else{
System.out.println("添加失败!");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//stmt.close();
//7. 释放资源
//避免空指针异常
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2.3.4.ResultSet:结果集对象,封装查询结果
-
boolean next(): 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true
-
resultSet.next(); 这个API就类似于我们的Iterator里面维护的游标,执行一次next() 方法,游标就移动一个位置
-
getXxx(参数):获取数据
- Xxx:代表数据类型 如: int getInt() , String getString()
- 参数:
- int:代表列的编号,从1开始 如: getString(1)
- String:代表列名称。 如: getDouble(“balance”)
- 注意:
- 使用步骤:
- 游标向下移动一行
- 判断是否有数据
- 获取数据
//循环判断游标是否是最后一行末尾。 while(rs.next()){ //获取数据 int id = rs.getInt(1); String name = rs.getString("name"); double balance = rs.getDouble(3); System.out.println(id + "---" + name + "---" + balance); }
- 使用步骤:
-
常用方法:
- resultSet.getInt(String columnName);
- resultSet.getString(String columnName);
- resultSet.Previous() 移动到前一行
- resultSet.beforeFirst() 移动到最前面
- resultSet.afterLast(); 移动到最后面
2.3.5.PreparedStatement:执行sql的对象
- SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
- 输入用户随便,输入密码:a’ or ‘a’ = 'a
- sql:select * from user where username = ‘fhdsjkf’ and password = ‘a’ or ‘a’ = ‘a’
- 解决sql注入问题:使用PreparedStatement对象来解决
- 预编译的SQL:参数使用?作为占位符
- 步骤:
- 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
- 注册驱动
- 获取数据库连接对象 Connection
- 定义sql
- 注意:sql的参数使用?作为占位符。 如:select * from user where username = ? and password = ?;
- 获取执行sql语句的对象 PreparedStatement :Connection.prepareStatement(String sql) 获取PrepareStatement对象
- 给?赋值:
- 方法: setXxx(参数1,参数2)
- 参数1:?的位置编号 从1 开始
- 参数2:?的值
- 方法: setXxx(参数1,参数2)
- 执行sql,接受返回结果,不需要传递sql语句
- 处理结果
- 释放资源
- 注意:后期都会使用PreparedStatement来完成增删改查的所有操作
- 可以防止SQL注入
- 效率更高
public void testPrepareStatementQuestion() throws SQLException {
// Statement statement = connection.createStatement();
// ? 表示占位符
// 这一句话,会对我们的sql语句去进行预编译,后续我们只需要把 ?填充进来即可,填充进来的都是值,不会是我们的关键字
PreparedStatement pst = connection.prepareStatement("select * from user where username = ? and password = ?");
//前面的数字表示第x个问号,在这里1表示第一个?,后面的表示第x个?所替换的值
pst.setString(1,"lanzhao");
pst.setString(2,"'lanzhao123' or 1=1 ");
// 执行sql语句,获取结果
ResultSet resultSet = pst.executeQuery();
while(resultSet.next()) {
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String gender = resultSet.getString("gender");
int id = resultSet.getInt("id");
System.out.println("id : " + id + ", username : " + username + ", password:" + password + ", gender:" + gender);
}
}
// 结论,我们通过执行测试,发现并没有找到对应的用户,说明我们这里通过这种预编译占位的方式,可以解决数据库的注入问题
2.4.JDBCUtils
- 目的:简化书写
- 注册驱动也抽取
- 抽取一个方法获取连接对象
- 需求:不想传递参数(麻烦),还得保证工具类的通用性。
- 解决:配置文件jdbc.properties
url=jdbc:mysql://localhost:3306/数据库名称?useSSL=false&characterEncoding=utf-8 user=账号 password=密码 dirver=com.mysql.jdbc.Driver
- 抽取一个方法释放资源
public class JDBCUtils {
private static String url;
private static String user;
private static String password;
private static String driver;
/** * 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块 */
static{
//读取资源文件,获取值。
try {
//1. 创建Properties集合类。
Properties pro = new Properties();
//获取src路径下的文件的方式--->ClassLoader 类加载器
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
URL res = classLoader.getResource("jdbc.properties");
String path = res.getPath();
System.out.println(path);// D:/IdeaProjects/itcast/out/production/day04_jdbc/jdbc.properties
//2. 加载文件
// pro.load(new FileReader("D:\\IdeaProjects\\itcast\\day04_jdbc\\src\\jdbc.properties"));
pro.load(new FileReader(path));
//3. 获取数据,赋值
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver");
//4. 注册驱动,其实就是使类加载,类的全路径名已经写在了properties里
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/** * 获取连接 * @return 连接对象 */
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
/** * 释放资源 * @param stmt * @param conn */
public static void close(Statement stmt,Connection conn){
if( stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/** * 释放资源 * @param stmt * @param conn */
public static void close(ResultSet rs,Statement stmt, Connection conn){
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();
}
}
}
}
- 需求练习
* 需求:
1. 通过键盘录入用户名和密码
2. 判断用户是否登录成功
* select * from user where username = "" and password = "";
* 如果这个sql有查询结果,则成功,反之,则失败
//1. 创建数据库表 user
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
PASSWORD VARCHAR(32)
);
INSERT INTO USER VALUES(NULL,'zhangsan','123');
INSERT INTO USER VALUES(NULL,'lisi','234');
2. 代码实现:
public class JDBCPractice {
public static void main(String[] args) {
//1.键盘录入,接受用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
//2.调用方法
boolean flag = new JDBCUtils().login(username, password);
//3.判断结果,输出不同语句
if(flag){
//登录成功
System.out.println("登录成功!");
}else{
System.out.println("用户名或密码错误!");
}
}
/** * 登录方法 */
public boolean login(String username ,String password){
if(username == null || password == null){
return false;
}
//连接数据库判断是否登录成功
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
//1.获取连接
try {
conn = JDBCUtils.getConnection();
//2.定义sql
String sql = "select * from user where username = '"+username+"' and password = '"+password+"' ";
//3.获取执行sql的对象
stmt = conn.createStatement();
//4.执行查询
rs = stmt.executeQuery(sql);
//5.判断
/* if(rs.next()){//如果有下一行,则返回true return true; }else{ return false; }*/
return rs.next();//如果有下一行,则返回true
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,stmt,conn);
}
return false;
}
}
2.5.JDBC批处理
- 业务场景:当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。
- 默认情况下Mysql的批处理仍然是一条一条执行,需要在url后面添加rewriteBatchedStatements=true参数
- 实现批处理有两种方式
- 第一种方式:
- Statement.addBatch(sql)
- 执行批处理SQL语句
- executeBatch()方法:执行批处理命令
- clearBatch()方法:清除批处理命令
- 优点:可以向数据库发送多条不同的SQL语句。
- 缺点:
- SQL语句没有预编译。
- 当向数据库发送多条语句相同,但仅参数不同的SQL语句时,需重复写上很多条SQL语句
// Statement批处理 public void testInsertBatch() throws SQLException { long startTime = System.currentTimeMillis(); // 这里这个batch想当是一个小推车,帮助我们更好的搬砖 for (int i = 0; i < 2000; i++) { String username = "兰钊" + i; statement.addBatch("insert into user values (null,'"+username+"','cskaoyan','male')"); } statement.executeBatch(); statement.close(); long endTime = System.currentTimeMillis(); System.out.println("statement批处理:" + (endTime -startTime)); }
- 实现批处理的第二种方式:
- 采用PreparedStatement.addBatch()实现批处理
- 优点:与数据库通信次数在批量操作时,PreparedStatment的通信次数远少于Statment。
- 缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
// prepareStatement 进行批处理 public void testPrepareStatementBatch() throws SQLException { PreparedStatement preparedStatement = connection.prepareStatement("insert into user values (null,?,?,?)"); long startTime = System.currentTimeMillis(); // 批量放入 for (int i = 0; i < 2000; i++) { String username = "李白龙" + i; preparedStatement.setString(1,username); preparedStatement.setString(2,"cskaoyan111"); preparedStatement.setString(3,"male"); preparedStatement.addBatch(); } // 批处理执行 int[] ints = preparedStatement.executeBatch(); long endTime = System.currentTimeMillis(); System.out.println("prepareStatement批处理:" + (endTime -startTime)); }
- 第一种方式:
2.6.JDBC控制事务
2.6.1.事务
- 一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
- 操作:
- 开启事务
- 提交事务
- 回滚事务
2.6.2.使用Connection对象来管理事务
- 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
- 在执行sql之前开启事务
- 提交事务:commit()
- 当所有sql都执行完提交事务
- 回滚事务:rollback()
- 在catch中回滚事务
2.6.3.Example
public class JDBCExample {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
try {
//1.获取连接
conn = JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//2.定义sql
//2.1 张三 - 500
String sql1 = "update account set balance = balance - ? where id = ?";
//2.2 李四 + 500
String sql2 = "update account set balance = balance + ? where id = ?";
//3.获取执行sql对象
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//4. 设置参数
pstmt1.setDouble(1,500);
pstmt1.setInt(2,1);
pstmt2.setDouble(1,500);
pstmt2.setInt(2,2);
//5.执行sql
pstmt1.executeUpdate();
// 手动制造异常
int i = 3/0;
pstmt2.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
//事务回滚
try {
if(conn != null) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,null);
}
}
}
2.7.DBUtils框架
2.7.1.DbUtils类
- 帮助程序员解析结果,并封装为对象或集合
- 提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的
- 主要方法
- public static void close(…) throws java.sql.SQLException:
- DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它就关闭Connection、Statement和ResultSet
- public static void closeQuietly(…):
- 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
- public static void commitAndCloseQuietly(Connection conn):
- 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
- public static boolean loadDriver(java.lang.String driverClassName):
- 这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。
- public static void close(…) throws java.sql.SQLException:
2.7.2.QueryRunner类
- 简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量
- 构造方法
- public QueryRunner()
- public QueryRunner(DataSource ds)
- 主要方法
- public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException
- 执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。
- 该方***自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
- public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException
- 几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造方法的数据源(DataSource) 或使用的setDataSource 方法中重新获得 Connection。
- public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException
- 执行一个不需要置换参数的查询操作。
- public int update(Connection conn, String sql, Object[] params) throws SQLException
- 用来执行一个更新(插入、更新或删除)操作。
- public int update(Connection conn, String sql) throws SQLException
- 用来执行一个不需要置换参数的更新操作。
// 查询 QueryRunner queryRunner = new QueryRunner(dataSource); queryRunner.query(sql,resultSetHandler); // 这种方式DBUtils会帮助我们关闭连接 queryRunner.query(sql,resultSetHandler,param...); // 这种方式DBUtils会帮助我们关闭连接 // 查询-传递进去连接 QueryRunner queryRunner = new QueryRunner(); // 这个地方就不传递数据源 queryRunner.query(connection,sql,resultSetHandler); // 这种方式DBUtils不会帮助我们关闭连接 queryRunner.query(connection,sql,resultSetHandler,param...); // 修改、删除、增加都是使用update方法 QueryRunner queryRunner = new QueryRunner(dataSource); queryRunner.update(sql,params...); queryRunner.update(connection,sql,params...);
- public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException
2.7.3.ResultSetHandler接口
- 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
- ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。
- ResultSetHandler底层是通过反射来实现的封装,User对象和数据库中的名称一定要相同,如果不相同则无法封装进集合里。
- 实现类
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map<列名,列值>里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
- ScarlarHandler:将单个值封装,可以用来统计聚合函数count(),max(),min(),avg()等方法返回的值
- BeanListHandler类
- 帮助我们把查询的结果转化为一个List
@Test public void testQueryBeanListHandler() throws SQLException { // 创建QueryRunner对象 QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource()); // 我们要执行sql String sql = "select * from user"; List<User> userList = queryRunner.query(sql, new BeanListHandler<User>(User.class)); System.out.println(userList); }
- BeanHandler类
- 帮助我们把查询的结果转化为一个Bean,注意这里是指我们查询结果的第一条记录
@Test public void testSelectOne() throws SQLException { QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource()); // 我们要执行sql String sql = "select * from user where id = ?"; //1这个参数相当于上边的?的值 User user = queryRunner.query(sql, new BeanHandler<User>(User.class),1); System.out.println(user); }
- ScalarHandler类
- 直接返回我们查询的结果(AVG、SUM、COUNT、MAX、MIN)
@Test public void testSelectCount() throws SQLException { QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource()); String sql = "select count(1) from user"; Object query = queryRunner.query(sql, new ScalarHandler<>()); System.out.println(query); }
- ColunmnListHandler类
- 查询单列的一个结果,封装到一个List里
// 查询单列 ColumnListHandler @Test public void testSelectNameList() throws SQLException { QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource()); String sql = "select username from user"; List<String> strList = queryRunner.query(sql, new ColumnListHandler<String>()); System.out.println(strList); }
2.7.4.DButils使用
//创建一个User类,封装成JavaBea的标准形式,并对应数据库的字段名称
public class User {
private Integer id;
private String username;
private String password;
private String gender;
//省略所必须要的get、set方法
}
//使用已经写好的Druid连接池
public class DruidUtils {
private static DataSource dataSource;
static {
// 加载配置文件
Properties properties = new Properties();
ClassLoader classLoader = DruidUtils.class.getClassLoader();
InputStream stream = classLoader.getResourceAsStream("druid.properties");
try {
properties.load(stream);
} catch (IOException e) {
e.printStackTrace();
}
// 创建一个Druid数据源工厂对象
DruidDataSourceFactory dataSourceFactory = new DruidDataSourceFactory();
try {
// 通过工厂创建一个数据源并且去赋值
dataSource = dataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
// 获取数据源
public static DataSource getDataSource(){
return dataSource;
}
}
//测试类
public class DButilsTest {
// DBUtils
@Test
public void testDBtils(){
// DbUtils.close();
// DbUtils.commitAndClose();
}
@Test
public void testQueryBasic(){
ArrayList<User> users = new ArrayList<>();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = DruidUtils.getConnection();
preparedStatement = connection.prepareStatement("select * from user");
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setGender(resultSet.getString("gender"));
users.add(user);
}
// 输出
System.out.println(users);
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.releaseSources(connection,preparedStatement,resultSet);
}
}
// 关于传不传数据源,
// 都是可以的,假如我们就是做简单的增删改查,就传递数据源
// 什么情况我们采用QueryRunner传递数据源的构造方法呢?
// 假如我们在使用事务的时候,就不能传递数据源了,因为事务要使用同一个connection对象
// QueryRunner
@Test
public void testQueryRunnerBasic() throws SQLException {
// 创建一个QueryRunner
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
// 调用方法
int affectedRows = queryRunner.update("delete from user where id = ?", 404);
System.out.println(affectedRows);
}
// QueryRunner delete
@Test
public void testQueryRunnerBasic2() throws SQLException {
ArrayList<User> users = new ArrayList<>();
// 创建一个QueryRunner
QueryRunner queryRunner = new QueryRunner();
Connection connection = DruidUtils.getConnection();
// 调用方法
int affectedRows = queryRunner.update(connection,"delete from user where id = ?", 4005);
System.out.println(affectedRows);
}
// QueryRunner update
@Test
public void testQueryRunnerUpdate() throws SQLException {
// 创建一个QueryRunner
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
// 调用方法
int affectedRows = queryRunner.update("update user set username = ? where id = ?", "lanzhao520",2);
System.out.println(affectedRows);
}
// QueryRunner insert
@Test
public void testQueryRunnerInsert() throws SQLException {
// 创建一个QueryRunner
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
String sql = "insert into user values (?,?,?,?)";
// 调用方法
int affectedRows = queryRunner.update(sql, 3,"小钻风","大王叫我来巡山","gender");
System.out.println(affectedRows);
}
// 关闭连接的问题:
// 如果采用的这种方式去创建QueryRunner: new QueryRunner(DruidUtils.getDataSource()), 那么会帮我们关闭连接
// 如果采用无参的方式去创建QueryRunner,那么就不会帮我们关闭连接
@Test
public void testQueryList1() throws SQLException {
// 创建QueryRunner对象
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
// 我们要执行sql
String sql = "select * from user";
List<User> userList = queryRunner.query(sql, new MyResultSetHandler());
System.out.println(userList);
}
// 这种方式仍然不好用,因为还是需要我们自己解析
// 使用已经封装好的实现类
// 查询一个List
@Test
public void testQueryBeanListHandler() throws SQLException {
// 创建QueryRunner对象
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
// 我们要执行sql
String sql = "select * from user";
List<User> userList = queryRunner.query(sql, new BeanListHandler<User>(User.class));
System.out.println(userList);
}
// 查询单个bean
@Test
public void testSelectOne() throws SQLException {
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
// 我们要执行sql
String sql = "select * from user where id = ?";
User user = queryRunner.query(sql, new BeanHandler<User>(User.class),1);
System.out.println(user);
}
// 查询聚合函数的值,例如count,avg,max
@Test
public void testSelectCount() throws SQLException {
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
String sql = "select count(1) from user";
Object query = queryRunner.query(sql, new ScalarHandler<>());
System.out.println(query);
}
// 查询单列 ColumnListHandler
@Test
public void testSelectNameList() throws SQLException {
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
String sql = "select username from user";
List<String> strList = queryRunner.query(sql, new ColumnListHandler<String>());
System.out.println(strList);
}
// 查询单个bean,测试反射猜想
@Test
public void testSelectOne2() throws SQLException {
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
// 我们要执行sql
String sql = "select * from user where id = ?";
UserVO user = queryRunner.query(sql, new BeanHandler<UserVO>(UserVO.class),1);
System.out.println(user);
}
// 实现批处理功能
@Test
public void testBatch() throws SQLException {
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
String sql = "insert into user values (?,?,?,?)";
Object[][] params = new Object[3][];
params[0] = new Object[]{
4,"金角大王","我叫你一声你敢答应吗","male"};
params[1] = new Object[]{
5,"铁扇公主","牛夫人","female"};
params[2] = new Object[]{
6,"女儿国国王","唐长老,留下来","female"};
// 这个batchRet数组对应的是每一条sql语句影响的行数
int[] batchRet = queryRunner.batch(sql, params);
for (int affectedRows : batchRet) {
System.out.println("影响的行数:" + affectedRows);
}
}
}
//使用DBUtils实现事务
public class TranferTest {
//这里没有使用事务,如果中途失败会导致账户损失
@Test
public void testTransfer1() {
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
Connection connection = DruidUtils.getConnection();
try {
connection.setAutoCommit(false);
String sql = "update account set money = money + ? where name = ?";
// 扣兰钊的钱 -200
int affectedRows = queryRunner.update(sql, -200, "兰钊");
if (affectedRows < 1) {
System.out.println("扣钱失败!");
return;
}
// 给雪茄加钱
int affectedRows2 = queryRunner.update(sql, 200, "雪茄");
if (affectedRows2 < 1) {
System.out.println("加钱失败!!");
connection.rollback();
}
connection.commit();
}catch (Exception ex) {
ex.printStackTrace();
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//这里的session不是同一个对象,也没有实现事务
@Test
public void testTransfer2() {
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
Connection connection = DruidUtils.getConnection();
try {
connection.setAutoCommit(false);
String sql = "update account set money = money + ? where name = ?";
// 扣兰钊的钱 -200
// 用的不是同一个Connection对象
int affectedRows = queryRunner.update(sql, -200, "兰钊");
if (affectedRows < 1) {
System.out.println("扣钱失败!");
return;
}
int i = 1/0; //除0异常,按理说应该回滚事务,但是这里并不能控制事务
// 给雪茄加钱
// 用的不是同一个Connection对象
int affectedRows2 = queryRunner.update(sql, 200, "雪茄");
if (affectedRows2 < 1) {
System.out.println("加钱失败!!");
connection.rollback();
}
connection.commit();
}catch (Exception ex) {
ex.printStackTrace();
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Test
public void testTransfer3() {
QueryRunner queryRunner = new QueryRunner();
//创建一个Connection对象
Connection connection = DruidUtils.getConnection();
try {
connection.setAutoCommit(false);
String sql = "update account set money = money + ? where name = ?";
// 扣兰钊的钱 -200
// 传入同一个Connection对象
int affectedRows = queryRunner.update(connection,sql, -200, "兰钊");
if (affectedRows < 1) {
System.out.println("扣钱失败!");
return;
}
int i = 1/0;
// 给雪茄加钱
// 传入同一个Connection对象
int affectedRows2 = queryRunner.update(connection,sql, 200, "雪茄");
if (affectedRows2 < 1) {
System.out.println("加钱失败!!");
connection.rollback();
}
connection.commit();
}catch (Exception ex) {
ex.printStackTrace();
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}finally {
// 关闭连接
DbUtils.closeQuietly(connection);
}
}
}
2.7.数据库连接池
2.7.1.概念
- 其实就是一个容器(集合),存放数据库连接的容器。当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
2.7.2.好处
- 节约资源
- 用户访问高效
2.7.3.实现:
-
标准接口:java提供了一个数据库标准接口DataSource,javax.sql包下的,需实现这个接口标准化
-
获取连接:getConnection()
-
归还连接:Connection.close()。如果连接对象Connection是从连接池中获取的,那么调用Connection.close()方法,则不会再关闭连接了。而是归还连接
connection是个JavaEE标准库中JDBC提供的,我们能实例化connection对象,是因为mysql提供的驱动的getConnection()方法返回了该接口的实现类,也就是说,我们定义的connection对象,本质是多态实现的。为了实现统一,我们不需要具体了解各大数据库厂商是如何实现这套接口的,只需要使用connection多态的统一定义方式就可以了。
-
一般不去实现它,数据库厂商来实现
-
DBCP:数据库连接池技术
-
C3P0:数据库连接池技术
-
Druid:数据库连接池实现技术,由阿里提供
2.7.4.DBCP
- 导入jar包,Maven项目导包配置如下
<!--DBCP--> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-pool</groupId> <artifactId>commons-pool</artifactId> <version>1.6</version> </dependency>
- 建立一个dbcp.properties文件,放在resources目录下
#连接设置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/28_jdbc username=root password=123456 #<!-- 初始化连接 --> initialSize=10 #最大连接数量 maxActive=50 #<!-- 最大空闲连接 --> maxIdle=20 #<!-- 最小空闲连接 --> minIdle=5 #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 --> maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。 connectionProperties=useUnicode=true;characterEncoding=utf-8;useSSL=false #指定由连接池所创建的连接的自动提交(auto-commit)状态。 defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态。 #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix) defaultReadOnly= #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。 #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=REPEATABLE_READ
- 创建DBCPUtils对象
public class DBCPUtils { // 声明一个数据源对象 private static DataSource dataSource; static { // 加载配置文件 Properties properties = new Properties(); ClassLoader classLoader = DBCPUtils.class.getClassLoader(); InputStream stream = classLoader.getResourceAsStream("dbcp.properties"); try { properties.load(stream); } catch (IOException e) { e.printStackTrace(); } // 涉及到了一个设计模式:工厂模式 // 创建一个数据源工厂对象 BasicDataSourceFactory basicDataSourceFactory = new BasicDataSourceFactory(); try { // 给Datasource赋值 dataSource = basicDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } // 写一个方法 获取连接 public static Connection getConnection(){ Connection connection = null; try { connection = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } }
2.7.5.C3P0
- 导入jar包 (两个) c3p0-0.9.5.2.jar 和 mchange-commons-java-0.2.12.jar ,不要忘记导入数据库驱动jar包
- Maven项目下导包配置如下
<!--c3p0--> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.4</version> </dependency>
- 需要说明的是,这个配置文件的名字必须是 c3p0-config.xml,并且必须放在Resources目录下
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/28_jdbc</property> <property name="user">root</property> <property name="password">123456</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config> </c3p0-config>
- 创建C3p0Utils对象
public class C3p0Utils { // 首先声明一个数据源对象 private static DataSource dataSource; static { // 给Datasource对象去赋值 dataSource = new ComboPooledDataSource(); } // 获取连接 public static Connection getConnection(){ Connection connection = null; try { connection = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } }
2.7.6.Druid
- 导入jar包 druid-1.0.9.jar
- maven项目导包配置如下
<!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.19</version> </dependency>
- maven项目导包配置如下
- 定义配置文件:
- 建立一个dbcp.properties文件,放在我们的resources目录下
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/数据库名称 username=root password=密码
- 建立一个dbcp.properties文件,放在我们的resources目录下
- 简略步骤如下
//1.创建数据库连接池对象 DataSource ds = new ComboPooledDataSource(); //2. 获取连接对象 Connection conn = ds.getConnection(); //3.加载配置文件 Properties pro = new Properties(); InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"); pro.load(is); //4.获取连接池对象 DataSource ds = DruidDataSourceFactory.createDataSource(pro); //5.获取连接 Connection conn = ds.getConnection();
- 创建DruidUtils对象
public class DruidUtils { private static DataSource dataSource; static { // 加载配置文件 Properties properties = new Properties(); ClassLoader classLoader = DruidUtils.class.getClassLoader(); InputStream stream = classLoader.getResourceAsStream("druid.properties"); try { properties.load(stream); } catch (IOException e) { e.printStackTrace(); } // 创建一个Druid数据源工厂对象 DruidDataSourceFactory dataSourceFactory = new DruidDataSourceFactory(); try { // 通过工厂创建一个数据源并且去赋值 dataSource = dataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } // 获取连接 public static Connection getConnection(){ Connection connection = null; try { connection = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } }
2.8.Spring JDBC
- Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发
- 步骤:
- 导入jar包
- 创建JdbcTemplate对象。依赖于数据源DataSource
- JdbcTemplate template = new JdbcTemplate(ds);
- 调用JdbcTemplate的方法来完成CRUD的操作
- update():执行DML语句。增、删、改语句
- queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合
注意:这个方法查询的结果集长度只能是1
- queryForList():查询结果将结果集封装为list集合
注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
- query():查询结果,将结果封装为JavaBean对象
- query的参数:RowMapper
一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装
- new BeanPropertyRowMapper<类型>(类型.class)
- queryForObject:查询结果,将结果封装为对象
一般用于聚合函数的查询
- Example
/* 需求: 1. 修改1号数据的 salary 为 10000 2. 添加一条记录 3. 删除刚才添加的记录 4. 查询id为1的记录,将其封装为Map集合 5. 查询所有记录,将其封装为List 6. 查询所有记录,将其封装为Emp对象的List集合 7. 查询总记录数 */
import cn.itcast.domain.Emp;
import cn.itcast.utils.JDBCUtils;
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.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
public class JdbcTemplate {
//Junit单元测试,可以让方法独立执行
//1. 获取JDBCTemplate对象
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
/** * 1. 修改1号数据的 salary 为 10000 */
@Test
public void test1(){
//2. 定义sql
String sql = "update emp set salary = 10000 where id = 1001";
//3. 执行sql
int count = template.update(sql);
System.out.println(count);
}
/** * 2. 添加一条记录 */
@Test
public void test2(){
String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
int count = template.update(sql, 1015, "郭靖", 10);
System.out.println(count);
}
/** * 3.删除刚才添加的记录 */
@Test
public void test3(){
String sql = "delete from emp where id = ?";
int count = template.update(sql, 1015);
System.out.println(count);
}
/** * 4.查询id为1001的记录,将其封装为Map集合 * 注意:这个方法查询的结果集长度只能是1 */
@Test
public void test4(){
String sql = "select * from emp where id = ? or id = ?";
Map<String, Object> map = template.queryForMap(sql, 1001,1002);
System.out.println(map);
//{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20}
}
/** * 5. 查询所有记录,将其封装为List */
@Test
public void test5(){
String sql = "select * from emp";
List<Map<String, Object>> list = template.queryForList(sql);
for (Map<String, Object> stringObjectMap : list) {
System.out.println(stringObjectMap);
}
}
/** * 6.1. 查询所有记录,将其封装为Emp对象的List集合 */
@Test
public void test6(){
String sql = "select * from emp";
List<Emp> list = template.query(sql, new RowMapper<Emp>() {
@Override
public Emp mapRow(ResultSet rs, int i) throws SQLException {
Emp emp = new Emp();
int id = rs.getInt("id");
String ename = rs.getString("ename");
int job_id = rs.getInt("job_id");
int mgr = rs.getInt("mgr");
Date joindate = rs.getDate("joindate");
double salary = rs.getDouble("salary");
double bonus = rs.getDouble("bonus");
int dept_id = rs.getInt("dept_id");
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
return emp;
}
});
for (Emp emp : list) {
System.out.println(emp);
}
}
/** * 6.2. 查询所有记录,将其封装为Emp对象的List集合 */
@Test
public void test6_2(){
String sql = "select * from emp";
List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
for (Emp emp : list) {
System.out.println(emp);
}
}
/** * 7. 查询总记录数 */
@Test
public void test7(){
String sql = "select count(id) from emp";
Long total = template.queryForObject(sql, Long.class);
System.out.println(total);
}
}