大家好,我是一只学弱狗,记录学习的点点滴滴!
优质文章
优质专栏
谨以此篇博客,结束时长整8个月的寒暑假生活。
畅聊JDBC
JDBC
概念
JDBC,是Java Database Connectivity的缩写,即Java数据库连接,先看搜狗百科上的解释:JDBC是一种用于执行SQL语句的Java API,由一组用Java语言编写的类和接口组成,它可以为多种关系型数据库提供统一访问,据此可以构建更高级的工具和接口,实现了所有这些面向标准的目标并且具有简单,严格类型定义且高性能实现的接口。
上面的解释我勉强接受,用简单的语言我们这样来描述:它是Sun公司定义的一套操作所有关系型数据库的规则,各个数据库厂商,像MySQL、SQL Server等等,他们自己去实现这些接口,提供数据库的驱动jar包,我们可以按照Sun公司的规则(接口)编程。
使用步骤
- 导入驱动jar包(或者使用maven构建)
- 注册驱动
- 获取数据库连接对象
- 定义sql语句
- 获取执行sql语句的对象
- 执行sql语句,接收返回结果
- 处理结果
- 释放资源
Maven依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
代码演示
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
//1.导入jar包
try {
//2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.获取连接对象
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "mysql");
//4.定义sql语句
String sql = "select * from tbl_user";
//5.获取执行sql语句的对象
statement = connection.createStatement();
//6.执行sql语句
resultSet = statement.executeQuery(sql);
//7.处理结果
while (resultSet.next()) {
String username = resultSet.getString("username");
String password = resultSet.getString("password");
System.out.println(username + " : " + password);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//8.释放资源
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
相关对象详解
疑问猜测
看过了上面的代码,有小伙伴不禁会问,注册驱动,不就是通过反射加载Driver类进内存么,怎么就注册驱动了啊?
其实刚开始,我也有这样的疑问,不妨打开源码看一下
噢,原来这儿是一个静态代码块,懂了懂了。
DriverManager
概念
驱动管理对象
功能
注册驱动
在Driver的静态代码块中,我们发现,通过DriverManager的静态方法registerDriver来注册的驱动

获取数据库连接
public static Connection getConnection(String url,String user,String password)
- url:指定连接路径
- user:用户名
- password:密码
Connection
概念
数据库连接对象
功能
获取执行sql的对象
Statement createStatement() //执行静态sql
PreparedStatement prepareStatement(String sql) //执行预编译sql
管理事务
//开启事务
void setAutoCommit(boolean autoCommit) //调用该方法设置参数为false,即开启事务
//提交事务
void commit()
//回滚事务
void rollback()
Statement
概念
用于执行静态sql语句并返回其生成结果的对象
常用方法
int executeUpdate(String sql) //执行增删改语句,返回影响的行数
ResultSet executeQuery(String sql) //执行查语句,返回ResultSet结果集对象
ResultSet
概念
结果集对象,封装查询结果
常用方法
boolean next() //游标向下移动一行,判断当前行是否是数据行,如果是数据行则返回true,否则返回false
XXX getXXX() //获取数据,该方法为重载方法,可以传入列数,也可以传入列名
案例:登陆验证功能
任何理论都离不开实践!
需求:用户在控制台输入用户名和密码,核对其正确性,整个过程存储为日志信息。
public static void main(String[] args) {
//待输入用户名
String username = null;
//待输入密码
String password = null;
//获取键盘输入流对象
Scanner in = new Scanner(System.in);
//日期对象
Date date = new Date();
//日期格式化对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
//缓冲字符输出流
BufferedWriter bw = null;
//数据库连接对象
Connection connection = null;
//执行sql语句对象
Statement statement = null;
//结果集对象
ResultSet resultSet = null;
try {
//获取类加载器
ClassLoader classLoader = Demo.class.getClassLoader();
//获取指定资源的URL路径
URL resource = classLoader.getResource("conf/log.txt");
//获取指定资源的绝对路径
String path = resource.getPath();
//文件字符输出流 追加字符
FileWriter fw = new FileWriter(path, true);
bw = new BufferedWriter(fw);
System.out.println("用户名:");
username = in.nextLine();
System.out.println("密码:");
password = in.nextLine();
connection = JDBCUtils.getConnection();
String sql = "select * from tbl_user where username = '" + username + "' and password = '" + password + "' ";
System.out.println(sql);
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
String format = sdf.format(date);
bw.newLine();
bw.write("时间:" + format);
bw.newLine();
bw.write("用户名:" + username);
bw.newLine();
bw.write("密码:" + password);
bw.newLine();
if (resultSet.next()) {
System.out.println("登录成功!");
bw.write("登录成功!");
} else {
System.out.println("登录失败!");
bw.write("登录失败!");
}
bw.newLine();
bw.write("--------------------------------------");
} catch (FileNotFoundException e) {
System.out.println("未发现指定文件");
System.exit(-1);
} catch (SQLException e) {
System.out.println("获取statement对象异常");
System.exit(-1);
} catch (IOException e) {
System.out.println("IO读写异常");
System.exit(-1);
} finally {
JDBCUtils.close(connection, statement, resultSet);
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意到,有个JDBCUtils类,我们看下这个类
public class JDBCUtils {
private static String driver = null;
private static String url = null;
private static String user = null;
private static String password = null;
static {
try {
Properties properties = new Properties();
/**
* 获取src路径下的文件的方式-->ClassLoader 类加载器
*/
properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("conf/jdbc.properties"));
driver = properties.getProperty("driver");
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
try {
Connection connection = DriverManager.getConnection(url,user,password);
return connection;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
public static void close(Connection connection, Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection connection, Statement statement, ResultSet resultSet){
close(connection,statement);
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
仔细观察,提高了代码的复用性,通过配置文件修改数据库的属性,是不是值得我们学习呢?
案例思考
上面的代码,还是很不错的,但是也面临着一个问题,sql注入问题
纳尼?不可思议,观察sql语句,对于聪明的你我就不做更多的解释了?如何解决?PreparedStatement,之前我们说过,它是一个执行预编译sql的对象,如何操作呢?
首先原先的步骤得先发生下改变
- 导入驱动jar包
- 注册驱动
- 获取数据库连接对象 Connection
- 定义sql语句,注:sql的参数使用?来代替
- 获取执行sql语句的对象PreparedStatement
- 给占位符?赋值
- 执行sql语句,接收返回结果
- 处理结果
- 释放资源
于是,我们有个加强版
public static void main(String[] args) {
String username = null;
String password = null;
Scanner in = new Scanner(System.in);
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
BufferedWriter bw = null;
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
ClassLoader classLoader = DemoPlus.class.getClassLoader();
URL resource = classLoader.getResource("conf/log.txt");
String path = resource.getPath();
FileWriter fw = new FileWriter(path, true);
bw = new BufferedWriter(fw);
/* FileOutputStream fos = new FileOutputStream(path,true);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);*/
System.out.println("用户名:");
username = in.nextLine();
System.out.println("密码:");
password = in.nextLine();
connection = JDBCUtils.getConnection();
String sql = "select * from tbl_user where username = ? and password = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
resultSet = preparedStatement.executeQuery();
String format = sdf.format(date);
bw.newLine();
bw.write("时间:" + format);
bw.newLine();
bw.write("用户名:" + username);
bw.newLine();
bw.write("密码:" + password);
bw.newLine();
if (resultSet.next()) {
System.out.println("登录成功!");
bw.write("登录成功!");
} else {
System.out.println("登录失败!");
bw.write("登录失败!");
}
bw.newLine();
bw.write("--------------------------------------");
} catch (FileNotFoundException e) {
System.out.println("未发现指定文件");
System.exit(-1);
} catch (SQLException e) {
System.out.println("获取statement对象异常");
System.exit(-1);
} catch (IOException e) {
System.out.println("IO读写异常");
System.exit(-1);
} finally {
JDBCUtils.close(connection, preparedStatement, resultSet);
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们再来sql注入一次,没门。。。
事务管理及演示
什么是事务?
指一个包含多个步骤的业务操作,如果这个业务被事务管理,则这多个步骤要么同时执行成功,要么同时失败。
模拟:转账
构建数据库表
我们的需求是张三给李四转1000元,期望结果张三余额是4000元,李四余额是6000元。
public static void main(String[] args) {
//获取数据库连接对象
Connection connection = JDBCUtils.getConnection();
try {
//开启事务
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
//定义sql语句
String sql = "update tbl_account set balance = balance + ? where id = ? ";
//获取执行预编译sql语句的PreparedStatement对象
PreparedStatement preparedStatement1 = null;
PreparedStatement preparedStatement2 = null;
try {
preparedStatement1 = connection.prepareStatement(sql);
preparedStatement1.setDouble(1,-1000);
preparedStatement1.setInt(2,1);
preparedStatement2 = connection.prepareStatement(sql);
preparedStatement2.setDouble(1,+1000);
preparedStatement2.setInt(2,2);
int count1 = preparedStatement1.executeUpdate();
//此处发生错误
int a = 3/0;
int count2 = preparedStatement2.executeUpdate();
//提交事务
connection.commit();
if(count1>0 && count2>0){
System.out.println("转账成功!");
}else{
System.out.println("转账失败!");
}
} catch (SQLException e) {
try {
//事务回滚
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtils.close(connection,preparedStatement1);
JDBCUtils.close(null,preparedStatement2);
}
}
以上代码,感兴趣慢慢研究,如果去掉了事务管理,会怎么样呢?
数据库连接池
概念
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
剖析
Java官方提供了DataSource接口,该接口由数据库厂商实现,用于构建数据库连接池,可以使用其方法来过去连接对象或者归还连接
C3P0数据库连接池
使用步骤
- 导入jar包
- 编写配置文件(注:文件必须是c3p0.properties或c3p0-config.xml)
- 通过
ComboPooledDataSource
创建DataSource对象 - 获取连接Connection对象
- 编写sql语句,执行并处理结果
- 归还连接对象
配置文件
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost/mybatis</property>
<property name="user">root</property>
<property name="password">mysql</property>
<!-- 初始化申请的连接数量 -->
<property name="initialPoolSize">10</property>
<!-- 最大的连接数量 -->
<property name="maxPoolSize">10</property>
<!-- 超时时间 -->
<property name="checkoutTimeout">3000</property>
</default-config>
<!-- This app is massive! -->
<named-config name="intergalactoApp">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost/mybatis</property>
<property name="user">root</property>
<property name="password">mysql</property>
<!-- 初始化申请的连接数量 -->
<property name="initialPoolSize">10</property>
<!-- 最大的连接数量 -->
<property name="maxPoolSize">10</property>
<!-- 超时时间 -->
<property name="checkoutTimeout">3000</property>
</named-config>
</c3p0-config>
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.导入jar包
//2.编写配置文件
//3.通过ComboPooledDataSource创建DataSource对象
DataSource dataSource = new ComboPooledDataSource();
//4.获取连接对象
connection = dataSource.getConnection();
//5.编写sql语句,执行并处理结果
String sql = "select * from tbl_account";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
double balance = resultSet.getDouble("balance");
System.out.println(id + " : " + name + " : " + balance);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
//6.归还连接对象
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Druid数据库连接池
使用步骤
- 导入jar包
- 编写配置文件(注:不同于c3p0,该配置文件可自定义名称及位置,通过properties来读取它)
- 通过
DruidDataSourceFactory
创建DataSource对象 - 获取连接Connection对象
- 编写sql语句,执行并处理结果
- 归还连接对象
配置文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=mysql
maxActive=10
maxWait=3000
public class JDBCUtils {
private static DataSource dataSource = null;
static {
try {
Properties properties = new Properties();
properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("conf/druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public static DataSource getDataSource(){
return dataSource;
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void close(Connection connection, Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection connection, Statement statement, ResultSet resultSet){
close(connection,statement);
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Spring JDBC
概念
Spring框架对JDBC的简单封装,简化JDBC的开发
常用方法
- 创建
JdbcTemplate
的对象,参数为一个数据库连接池DataSource
对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(JDBCUtils.getDataSource());
- 使用
update
方法来实现增删改,返回影响的行数
String sql = "update tbl_account set balance = ? where id = ?";
int count = jdbcTemplate.update(sql, balance, id);
String sql = "insert into tbl_account values(null,?,?)";
int count = jdbcTemplate.update(sql, name, balance);
String sql = "delete from tbl_account where id = ?";
int count = jdbcTemplate.update(sql, id);
- 使用
queryForMap
方法来返回数据,注:该查询的数据集的长度只能是1,列名作为key,值为value
String sql = "select * from tbl_account where balance = ?";
Map<String, Object> map = jdbcTemplate.queryForMap(sql,balance);
- 使用
queryForList
方法来返回数据集,注:该方式是先将每条数据封装为Map集合,再装载到List集合中
String sql = "select * from tbl_account";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
- 使用
queryForObject
方法来返回指定数值,注:该方式一般用于聚合函数的查询
String sql = "select count(*) from tbl_account";
Long total = jdbcTemplate.queryForObject(sql, long.class);
- 使用
query
方法来返回指定对象的数值
**方式一:**自己写RowMapper接口的方法
**方式二:**使用BeanPropertyRowMapper实现类
String sql = "select * from tbl_account";
/*List<Account> list = jdbcTemplate.query(sql,new RowMapper<Account>(){
@Override
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
Account account = new Account();
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
double balance = resultSet.getDouble("balance");
account.setId(id);
account.setName(name);
account.setBalance(balance);
return account;
}
});*/
List<Account> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));