JDBC
JDBC(Java DataBase Connectivity )概念:Java数据库连接,就是通过Java语言操作数据库。
JDBC本质: 其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
大致关系图如下:
JDBC快速入门
使用JDBC一般步骤如下:
- 导入驱动jar包(不同版本MySQL的jar包需求不同)
我所用的MySQL是5.5版本的,所以我用的是5.x版本的jar包,如:mysql-connector-java-5.1.37-bin.jar
我们要把我们jar加入到我们的项目中,操作步骤:
①复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下
②右键-->Add As Library
- 注册驱动
- 获取数据库连接对象 Connection
- 定义sql
- 获取执行sql语句的对象 Statement
- 执行sql,接受返回结果
- 处理结果
- 释放资源(资源不释放会占用资源,该现象称为内存泄露)
代码如下:
我想把经济部改为服务部
public static void main(String[] args) {
Connection connection=null;
Statement statement=null;
try {
//1. 导入驱动jar包
//2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接对象
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dl1", "root", "root");
//4.定义sql语句
String sql="update dept set name='经济部' where id=1";
//5.获取执行sql的对象 Statement
statement = connection.createStatement();
//6.执行sql
int i = statement.executeUpdate(sql);
//7.处理结果
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
}finally {
//8.释放资源
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
结果如下
接下来我来讲一下上面出现的对象详解各个对象
DriverManager:驱动管理对象
功能:
- 注册驱动:告诉程序该使用哪一个数据库驱动jar
方法 | 说明 |
static void registerDriver(Driver driver) | 注册与给定的驱动程序 DriverManager |
但我们注册驱动写的代码为:Class.forName("com.mysql.jdbc.Driver");
我们查看源码可以发现发现:在com.mysql.jdbc.Driver类中存在静态代码块,里面使用了这个方法,所以我们只需把Driver类的代码加载进内存就行
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。
因为我们我们导入的jar包中的java.sql.Driver中有com.mysql.jdbc.Driver 这一行内容,我们不注册驱动的话内部会自己找到这个文件注册驱动的。
- 获取数据库连接:
DriverManager方法 | 说明 |
static Connection getConnection(String url, String user, String password) | 获取数据库连接对象 |
参数一:url:指定连接的路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
例:jdbc:mysql://localhost:3306/db1
- 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:
jdbc:mysql:///数据库名称
参数二:user:用户名
参数三:password:密码
Connection:数据库连接对象
数据库连接对象Connection其实就是Java代码和数据库之间的桥梁对象
功能:
- 获取执行sql 的对象(Statement和PreparedStatement)
Connection方法 | 说明 |
Statement createStatement() | 获取Statement对象 |
PreparedStatement prepareStatement(String sql) | 获取PreparedStatement对象 |
- 管理事务:
Connection方法 | 说明 |
setAutoCommit(boolean autoCommit) | 设置参数为false,开启事务 |
commit() | 提交事务 |
rollback() | 回滚事务 |
Statement:执行sql的对象
Statement执行sql的方法 | 说明 |
boolean execute(String sql) | 可以执行任意的sql(了解) |
int executeUpdate(String sql) | 执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句 |
ResultSet executeQuery(String sql) | 执行DQL(select)语句 |
注意:int executeUpdate(String sql) 该方法的返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功,返回值>0的则执行成功,反之,则失败。
练习:
操作accout表,如下:
- account表 添加一条记录
public static void main(String[] args) throws SQLException {
Connection conn=null;
Statement stmt=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dl1", "root", "root");
stmt = conn.createStatement();
String sql="insert into account values (null,'王五',null)";
int i = stmt.executeUpdate(sql);
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
conn.close();
}
}
操作后表的变化
- account表 修改记录,把王五的balance变为1000,只需把sql语句改一下,其他的不用变
String sql="update account set balance=1000 where id=3";
操作后表的变化
- account表 删除一条记录
改一下sql为
String sql="delete from account where id=3";
操作后表的变化
ResultSet:结果集对象,封装查询结果
前面我们提到的Statement的一个方法:ResultSet executeQuery(String sql) 返回的结果即为结果集对象(ResultSet),因为我们查询的是一张表的结果,这时会出现一个光标,也叫做游标。
Resultset光标最初位于第一行之前,第一次调用方法next()使第一行成为当前行;第二次调用使第二行成为当前行,依此类推。
当调用next方法返回false时,光标位于最后一行之后。
ResultSet方法 | 说明 |
boolean next() | 游标向下移动一行,判断当前行是否是最后一行末尾(即是否有数据),如果是,则返回false,如果不是则返回true |
我们让光标下移的目的是为了得到数据,我们获取数据的方法为
ResultSet方法 | 说明 |
getXxx(参数) | 获取数据 |
- Xxx:代表数据类型。【如:数据为int类型,方法为getInt(),为String字符串,方法为:getString()】
该方法的参数有两种数据类型:
- int:代表列的编号,从1开始 如: getString(1):获得改行数据的第一个数据
- String:代表列名称。 如: getDouble("balance"):获得改行字段对应的数据
使用步骤:
- 游标向下移动一行
- 判断是否有数据
- 获取数据
练习:
- 定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。
步骤:
- 定义Emp类
- 定义方法 public static List findAll(){}
- 实现方法 select * from emp;
Emp代码(对应的getter,setter和toString方法自己写)
public class Emp {
private int id;
private String name;
private String gender;
private double salary;
private Date join_date;
private int dept_id;
}
操作代码
public static void main(String[] args){
List<Emp> list=findAll();
for (Emp emp : list) {
System.out.println(emp);
}
}
public static List<Emp> findAll(){
ArrayList<Emp> emps=new ArrayList<>();
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dl1", "root", "root");
stmt = conn.createStatement();
String sql="select*from emp";
rs = stmt.executeQuery(sql);
while (rs.next()){
Emp emp=new Emp();
emp.setId(rs.getInt("id"));
emp.setName(rs.getString(2));
emp.setGender(rs.getString(3));
emp.setSalary(rs.getDouble("salary"));
emp.setJoin_date(rs.getDate("join_date"));
emp.setDept_id(rs.getInt("dept_id"));
emps.add(emp);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return emps;
}
PreparedStatement:执行sql的对象
前面我们已经提到过获取该对象的方法
方法 | 说明 |
PreparedStatement prepareStatement(String sql) | 获取PreparedStatement对象 |
PreparedStatement继承了Statement接口,所以功能比Statement更强大
我们以后会经常使用PreparedStatement,因为Statemenet会出现SQL注入问题。
SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题.
比如用户登陆时:输入用户为任意值,输入密码:'a' or 'a' = 'a'
select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a'
使用PreparedStatement对象就可以解决sql注入问题
我们写的SQL就为预编译的SQL:参数使用 “?” 作为占位符
PreparedStatement的操作步骤:
- 导入驱动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)
- 给?赋值:
- 方法: setXxx(参数1,参数2)
- 参数1:?的位置编号 从1 开始
- 参数2:?的值
- 执行sql,接受返回结果,不需要传递sql语句
- 处理结果
- 释放资源
操作代码如下:
public static void main(String[] args) {
Connection conn=null;
PreparedStatement pstmt=null;
ResultSet rs=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn= DriverManager.getConnection("jdbc:mysql:///dl1","root","root");
String sql="select*from emp where id=? or id =?";
pstmt=conn.prepareStatement(sql);
pstmt.setInt(1,1);
pstmt.setInt(2,2);
rs = pstmt.executeQuery();
while (rs.next()){
System.out.println(rs.getInt(1)+rs.getString("NAME"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
pstmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
(后期都会使用PreparedStatement来完成增删改查的所有操作)
好处:
- 可以防止SQL注入
- 效率更高
为什么使用PrepareStatement对象能解决SQL注入问题?
- sql的执行需要编译,注入问题之所以出现,是因为用户填写 sql语句参与了编译
- 使用PrepareStatement对象在执行sql语句时,会将sql语句“运送”到mysql上编译,再回到 java端拿到参数运送到mysql端,用户填写的sql语句,就不会参与编译,只会当做参数来看,避免了sql注入问题
- PrepareStatement 在执行母句相同,参数不同的批量执行时,因为只会编译一次,节省了大量编译时间,效率会高
抽取JDBC工具类 : JDBCUtils
我们前面在写的时候发现有许多步骤都是一样的,为了节省一些操作,我们可以定义一个工具类,如果有需要用的地方直接调用就行了。
定义JDBCUtils的目的:简化书写
我们分析一下那些可以步骤可以把方法抽取出来:
把方法抽取出来:就是重构->抽取方法,简单来说就是把部分相关代码抽取出来成为一个方法,原来代码的位置改由调用该方法来代替
- 注册驱动
- 获取连接对象
- 需求:不想传递参数(麻烦),还得保证工具类的通用性。
- 解决:写一个配置文件
jdbc.propertiesurl= user= password=
如:
- 释放资源
代码展示:
public class JDBCUtils {
static String url=null;
static String user=null;
static String password=null;
static {
try {
Properties properties=new Properties();
properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
url=properties.getProperty("url");
user=properties.getProperty("user");
password=properties.getProperty("password");
//注册驱动
Class.forName(properties.getProperty("driver"));
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
//获取连接对象
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,user,password);
}
//释放资源
public static void close(Statement stmt, Connection conn){
close(null,stmt,conn);
}
public static void close(ResultSet rs,Statement stmt,Connection conn){
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (rs!=null) {
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (rs!=null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
JDBC控制事务
我们上一篇文章已经说过了事务是什么,我们简单说一下
事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
事务操作:
- 开启事务
- 提交事务
- 回滚事务
上面说过使用Connection对象来管理事务的方法
Connection方法 | 说明 |
setAutoCommit(boolean autoCommit) | 设置参数为false,开启事务 |
commit() | 提交事务 |
rollback() | 回滚事务 |
一般在catch中进行回滚事务操作
public static void main(String[] args) {
Connection conn=null;
Statement stmt=null;
try {
conn=JDBCUtils.getConnection();
conn.setAutoCommit(false);
String sql="update account set balance=1500";
stmt=conn.createStatement();
int i = stmt.executeUpdate(sql);
System.out.println(i);
System.out.println(10/0);
conn.commit();
} catch (Exception e) {
try {
conn.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
e.printStackTrace();
}finally {
JDBCUtils.close(stmt,conn);
}
}
数据库连接池
概念:
- 其实就是一个容器(集合),存放数据库连接的容器。
- 当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
好处:
- 节约资源
- 用户访问高效
实现:
标准接口:DataSource(在javax.sql包下)
方法:
方法 | 说明 |
getConnection() | 获取连接 |
close() | 归还连接 |
注意:如果连接对象Connection是从连接池中获取的,那么调用close() 方法,则不会再关闭连接了,而是归还连接。
(一般我们不去实现它,有数据库厂商来实现,我们说两种数据库连接池技术)
- C3P0:数据库连接池技术
- Druid:数据库连接池实现技术,由阿里巴巴提供的
C3P0:数据库连接池技术
使用步骤:
- 导入jar包 (两个) c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar(注意:不要忘记导入数据库驱动jar包)
- 定义配置文件:
- 名称: c3p0.properties 或者 c3p0-config.xml
- 路径:直接将文件放在src目录下即可。
- 创建核心对象,即数据库连接池对象:ComboPooledDataSource
- 获取连接: getConnection
相关代码:
创建数据库连接池对象
DataSource ds = new ComboPooledDataSource();
- 获取连接对象
Connection conn = ds.getConnection();
Druid:数据库连接池实现技术,由阿里巴巴提供的
使用步骤:
- 导入jar包 druid-1.0.9.jar
- 定义配置文件:
- 是properties形式的
- 可以叫任意名称,可以放在任意目录下
- 加载配置文件
- 获取数据库连接池对象:通过工厂类来获取 DruidDataSourceFactory
- 获取连接:getConnection
定义工具类
步骤:
- 定义一个类 JDBCUtils
- 提供静态代码块加载配置文件,初始化连接池对象
- 提供方法
把方法抽取出来
- 获取连接方法: 通过数据库连接池获取连接
- 释放资源
- 获取连接池的方法
JDBCUtils工具类代码
public class JDBCUtils {
private static DataSource ds=null;
static {
try {
Properties p=new Properties();
p.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
ds=DruidDataSourceFactory.createDataSource(p);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
public static void close(Statement stmt,Connection conn){
close(null,stmt,conn);
}
public static void close(ResultSet rs, Statement stmt,Connection conn){
try {
if (rs!=null) {
rs.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if (stmt!=null) {
stmt.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if (conn!=null) {
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static DataSource getDataSource(){
return ds;
}
}
为什么要使用数据库连接池
- 数据库连接创建是一个非常昂贵的操作,我们应该尽可能避免频繁创建连接。
- 传统的数据库访问方式,每次操作数据库都需要打开、关闭物理连接,非常耗费系统资源,性能差。而且数据库支持的连接数是有限的,创建大量的连接会导致数据库僵死。
- 使用连接池,系统初始化后会创建容器,会申请一些连接对象,当需要访问数据库时,从容器中取连接对象直接使用,数据库操作结束后归还给容器。
- 优点是节约资源,提高访问数据库的性能。
Spring JDBC:JdbcTemplate
Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发
使用步骤:
- 导入jar包(相关jar包可以点击下方链接下载)
链接: JdbcTemplate
- 创建JdbcTemplate对象。依赖于数据源DataSource(我们上面定义的工具类就有用了)
JdbcTemplate template = new JdbcTemplate(ds);
- 调用JdbcTemplate的方法来完成CRUD的操作
方法名称 | 说明 |
update() | 执行DML语句。增、删、改语句 |
queryForMap() | 查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合 |
queryForList() | 查询结果将结果集封装为list集合 |
query() | 查询结果,将结果封装为JavaBean对象 |
queryForObject() | 查询结果,将结果封装为对象(一般用于聚合函数的查询) |
query()中有一个参数:RowMapper
- 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装(直接new一个)
new BeanPropertyRowMapper<类型>(类型.class)
我们先创建一个JdbcTemplate对象
JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDataSource());
我接下来说一下这些方法的大致使用
update()
@Test
public void test1(){
//2. 定义sql
String sql = "...";
//3. 执行sql
int count = template.update(sql);
System.out.println(count);
}
update()(?作为占位符)
@Test
public void test2(){
String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
int count = template.update(sql, 第一个?的值, 第二个?的值, 第三个?的值);
System.out.println(count);
}
queryForMap()
@Test
public void test3(){
String sql = "select * from emp where id = ? or id = ?";
Map<String, Object> map = template.queryForMap(sql, 第一个?的值, 第二个?的值);
System.out.println(map);
//结果为这种形式
//{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20}
}
queryForList()
@Test
public void test4(){
String sql = "select * from emp";
List<Map<String, Object>> list = template.queryForList(sql);
for (Map<String, Object> stringObjectMap : list) {
System.out.println(stringObjectMap);
}
}
query()(参数用RowMapper)
@Test
public void test5(){
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();
emp.setId(rs.getInt("id"));
emp.setEname(rs.getString("ename"));
emp.setJob_id(rs.getInt("job_id"));
emp.setMgr(rs.getInt("mgr"));
emp.setJoindate(rs.getDate("joindate"));
emp.setSalary(rs.getDouble("salary"));
emp.setBonus(rs.getDouble("bonus"));
emp.setDept_id(rs.getInt("dept_id");
return emp;
}
});
for (Emp emp : list) {
System.out.println(emp);
}
}
query()(参数用BeanPropertyRowMapper)
@Test
public void test5_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);
}
}
queryForObject()
@Test
public void test6(){
String sql = "select count(id) from emp";
//sql语句count(列名)返回的值是long型值
Long total = template.queryForObject(sql, Long.class);
System.out.println(total);
}