搬运自bilibili
概念
JDBC:Java DataBase Connectivity,Java 数据库连接,Java语言操作数据库
JDBC的本质:其实就是官方(SUN公司)定义的一套操作所有关系型数据库的规则,也就是接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
快速入门
步骤:
1. 导入驱动jar包
2. 注册驱动
3 .获取数据库连接对象 Connection
4. 定义sql
5. 获取执行sql语句的对象 Statement
6. 执行sql,接收返回的结果
7. 处理结果
8. 释放资源
下面是一个一段家娃栗子:
package cn.itcast.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class JDBCDemo {
public static void main(String[] args) throws Exception {
//1.导入驱动jar包
//2.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//3.获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xuyuwei?characterEncoding=utf8&serverTimezone=Asia/Shanghai","root","root");
//4.定义sql语句
String sql = "INSERT INTO account VALUES (NULL,'张三',1000);";
//5.获取执行sql的对象,Statement
Statement stmt = conn.createStatement();
//6.执行对象
int count = stmt.executeUpdate(sql);
//7.处理结果
System.out.println(count);
//8.释放资源
stmt.close();
conn.close();
}
}
/**
* 在JDBCDemo中的写法会有一些问题,比如在某处程序出错停止后,后面的.close()方法没办法执行
* 造成内存泄漏,下面将写一个修改的版本
* */
package cn.itcast.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCDemo2 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.通过DriverManager获取连接对象
conn = DriverManager.getConnection("jdbc:mysql:///xuyuwei?serverTimezone=Asia/Shanghai", "root", "123456");
//3.编写sql语句
String sql = "CREATE TABLE stu (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(20),class VARCHAR(20))";
//4.获取执行sql的对象
stmt = conn.createStatement();
//5.执行sql
int returnNum = stmt.executeUpdate(sql);//当执行的是DDL语句的时候返回值为0
//6.显示执行结果
System.out.println(returnNum);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//7.关闭连接
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
看到上面那么多类那么多对象,早就懵圈了吧,下面来港一下各个类和对象的作用:
1.DriverManager:驱动管理对象
*功能:
1.注册驱动:告诉程序该使用哪一个数据库驱动jar
public static void registerDriver(Driver driver);//DriverManager中的静态方法
所以咋注册(调用这个方法)呢?
答:Class.forName("com.mysql.cj.jdbc.Driver");
使用反射机制加载Driver类
为毛使用上面的代码就可以注册呢?
答:Driver类被加载时会执行其中的静态代码块,查看Driver类的静态块:
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*注意:mysql5之后的驱动jar包可以忽略注册的步骤
2.获取数据库连接
*方法:public static Connection getConnection(String url,
String user,
String password);
*参数:
*url:指定连接的路径
*语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?编码方式&时区 //?之后的可以不设置
*user:用户名
*password:密码
2.Connection:数据库连接对象
1.功能:
1.获取执行sql的对象(Statement的对象)
*Statement createStatement()
*PreparedStatement prepareStatement(String sql)
2.管理事务
*开启事务:void setAutoCommit(boolean autoCommit) //当autoCommit=false时,开启事务(关闭自动提交)
*提交事务:commit()
*回滚事务:rollback()
3.Statement:执行sql语句的对象
1.功能:执行sql
1.boolean execute(String sql) //执行给定的SQL语句,这可能会返回多个结果。
2.int executeUpdate(String sql) //执行DML(INSERT,UPDATE,DELETE)语句、
DDL(CREATE,ALTER,DROP)语句。一般很少执行DDL语句。在执行DML语句时,返回
值:sql语句执行后影响的行数,可以通过这个返回值来判断DML语句是否执行成功,
返回值>0时代表执行成功。
3.ResultSet executeQuery(String sql) //执行给定的SQL语句,该语句返回单个 ResultSet对象(看下面)。
4.ResultSet:结果集对象,封装查询到的结果
方法:
1.next()方法:使游标向下移动一行
2.getXxx(参数):获取表中的数据
*Xxx代表数据类型,比方:getInt(),getString()
*参数:
1.int:代表该条查询记录的列号,从1开始。如getInt(1);
2.String:代表列名称。如:getString(name); //name是表中字段的名称
/**
* 这里要举例使用JDBC的查询功能,这里只是举个栗子,并不是最标准的写法。
* 要关注的就是另一个比较重要的类——ResultSet
* */
//3.编写sql语句
String sql = "SELECT * FROM employee";
//4.获取执行sql的对象
stmt = conn.createStatement();
//5.执行sql
rs = stmt.executeQuery(sql);//当执行的是DDL语句的时候返回值为0
//6.显示执行结果
rs.next(); //指针初始位置是在表头位置,调用.nest()方法使指针下移到第一条记录(指针不下移会报异常)
int id = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
System.out.println("员工编号:"+id+"\n员工姓名"+name+"\n员工年龄是"+age);
//7.关闭连接
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
注意.next()方法,它有两个功能:
1.使得指针向下移动一行
2.返回一个boolean类型的返回值
true:该行是有记录的
false:改行没有记录了(位于最后一行记录之后)
因此,上面的java代码是比较腊鸡的写法:步骤6可以改进一下:
//6.显示执行结果
while(rs.next()){ //打印所有的记录
int id = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
System.out.println("员工编号:"+id+"\n员工姓名:"+name+"\n员工年龄:"+age);
System.out.println("==============");
}
5.PreparedStatement
PreparedStatement是一个执行动态sql的接口。为毛要PreparedStatement?因为它更灵活,还可以有效的防止sql注入攻击。
百度一下啥是sql注入:SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
举个小栗子,假如有一个用户名密码登录系统,要用户名密码一致才能登录。
//伪代码
String sql = "select * from table where userid = "+userid+"and password ="+ password;
if (statement.executeQuery(sql).next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
如果password = “ ‘abc’ or ‘a’ = ‘a’ ”
则查询的结果为全表。判断处就会打印登录成功。
因此,静态的sql语句确实会有安全问题。下面说一下PreparedStatement的用法:
String sql = "select * from table where userid = ? and password = ?";
PreparedStatement pstmt = conn.preparedstatement(sql);
pstmt.setString(1,userid);
pstmt.setString(2,password);
ResultSet rs = pstmt.executeQuery();
问号为占位符,setXxx(占位符下标,传递参数)。占位符下标从1开始。
建立JDBCUtil(使得代码更简洁)
新建一个类,取名JDBCTool。主要完成:
-
注册驱动
Class.forname(""); -
获取Connection对象
DriverManager.getConnection(url,user,password); -
关闭数据库连接
xxx.close();
下面将详细介绍下这个类的作用:
- 首先为了后期不修改代码,数据库连接中的driver,url,user,password应当写在配置文件中;
步骤如下:- 在src目录下新建一个file,命名为jdbc.properties,在配置文件中写入:
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/xuyuwei?characterEncoding=utf8&serverTimezone=Asia/Shanghai
user = root
password = root
2.读取配置文件的方法:
//1.创建properties集合类
Properties pro = new Properties();
//2.加载配置文件,其中path为jdbc.properties的绝对文件路径
pro.load(new FileReader(path));
//3.读取配置文件中的内容
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");
String driver = pro.getProperty("driver");
这么做有个问题,就是读取配置文件的路径不是动态的,如果路径换了,那就gg了。
因此需要动态的获取路径。那咋整呢?
这就需要用到ClassLoader这个类了。
步骤:
1.使用反射机制获取JDBCTool类的类对象,通过类对象获取类加载器ClassLoader
ClassLoader cl = JDBCTool.class.getClassLoader();//获取ClassLoader对象
//此处的类只要是该项目中的类就可以,比如可以将JDBCTool替换为另一个类JDBCDemo
2.使用类加载器获取文件的路径
URL res = cl.getResource("cn/itcast/jdbc/jdbc.properties");//获取配置文件的url
//cl默认的根目录是src目录,后面getResource方法中输入properties文件的路径,上面展示
//是将properties文件放在jdbc包中
3.将路径对象变为String类型
String path = res.getPath();//将URL对象转换为字符串
至此,配置文件路径的动态链接就完成了。
上述操作只需要执行一次,因此,驱动注册和获取配置文件属性的操作放在类的静态块中。
- 获取Connection对象:
话不多说直接上代码:
/**
* 获取Connection对象
* @return conn对象
*/
public static Connection getConnection(){
Connection conn = null;
try {
conn = DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
- 关闭数据库连接
关闭有两种情况
- 如果是执行的操作语句那就关闭Statement和Connection。
- 如果是执行的查询语句那就关闭ResultSet,Statement和Connection。
这里就需要写两个方法,来执行不同的情况。来用用java多态:
/**
* 执行操作语句时调用
* @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 rs
* @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();
}
}
}
}
好了JDBCTool类的功能就这么多。
.
数据库连接池
标准: 由sun公司制定的标准,具体实现由第三方厂家来完成。
标准接口:javax.sql.DataSource
接口方法getConnection()来获取连接,close()方法归还连接。
实现技术:
①C3P0 :数据库连接池技术
②Druid :数据库连接池技术,由阿里巴巴公司提供
使用C3P0的方法
- 首先需要导入jar包:①c3p0-0.9.5.5.jar ②mchange-commons-java-0.2.19.jar(记得先导入第三方驱动jar包)
- 配置文件:c3p0.properties 或者 c3p0-config.xml。自己写个xml放到src目录下就可以了。
c3p0-config.xml:
<c3p0-config>
<!--使用默认的配置读取参数-->
<default-config>
<property name = "driverClass">com.mysql.cj.jdbc.Driver</property>
<!---->
<property name = "jdbcUrl">jdbc:mysql://localhost:3306/xuyuwei?characterEncoding=utf8&serverTimezone=Asia/Shanghai</property>
<property name = "user">root</property>
<property name = "password">root</property>
<!--连接池参数-->
<!--初始化申请的连接数量-->
<property name = "initialPoolSize">5</property>
<!--最大连接数量-->
<property name = "maxPoolSize">10</property>
<!--超时时间-->
<property name = "checkoutTimeout">3000</property>
</default-config>
<!--使用其他的配置读取参数-->
<named-config name = "otherc3p0">
<property name = "driverClass">com.mysql.cj.jdbc.Driver</property>
<property name = "jdbcUrl">jdbc:mysql://localhost:3306/xuyuwei?characterEncoding=utf8&serverTimezone=Asia/Shanghai</property>
<property name = "user">root</property>
<property name = "password">root</property>
<!--连接池参数-->
<property name = "initialPoolSize">5</property>
<property name = "maxPoolSize">7</property>
<property name = "checkoutTimeout">3000</property>
</named-config>
</c3p0-config>
这里解释一下配置文件:
maxPoolSize:连接池最多能容纳多少连接
checkoutTimeout:当获取连接失败(达到最大连接了),静默多长时间后抛出异常。
- 创建连接池对象:CombopooledDataSource
- 获取连接:使用getConnection()方法获取连接
import java.sql.Connection;
import java.sql.SQLException;
public class C3P0Demo1 {
public static void main(String[] args) throws SQLException {
//1.创建连接池对象
DataSource ds = new ComboPooledDataSource();//使用默认配置
// DataSource ds = new ComboPooledDataSource("otherc3p0");//使用自定义配置
//2.获取连接
Connection conn = ds.getConnection();
}
}
使用Druid数据库连接池
- 第一步还是导包:druid-1.1.9.jar
- 定义配置文件:
使用properties配置文件,可以放在任意路径,任意名称。需手动导入配置。 - 通过一个工厂类DruidDataSourceFactory来获取数据库连接池对象。
- 获取连接getConnection()。
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
public class DruidDemo1 {
public static void main(String[] args) throws Exception {
//1.加载配置文件
//1.1将配置文件读入字节流
InputStream is = DruidDemo1.class.getClassLoader().getResourceAsStream("cn/itcast/druid/druid.properties");
//1.2新建一个Properties对象
Properties pro = new Properties();
//1.3将获取的字节流附给pro对象
pro.load(is);
//2.通过工厂类来得到
DataSource ds = new DruidDataSourceFactory().createDataSource(pro);
//3.获取连接
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
编写工具类
为了使得代码更规范更整洁,编写工具类。
首先要明确工具类JDBCUtils的作用是什么。
- 创建数据库连接池。(需要一加载就能连接,那就用static块)
- 返回数据库连接对象(通过类直接使用,那就用static方法)
- 返回数据库连接池对象
- 关闭ResultSet和Statement,归还数据库连接。
明确了这四个功能,工具类就能完成了。
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JDBCUtils {
//定义成员变量
private static DataSource ds;
static {
try {
//加载配置文件
Properties pro = new Properties();
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("cn/itcast/datasource/druid/druid.properties"));
//新建线程池对象
ds = new DruidDataSourceFactory().createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获得数据库连接
* @return Connection
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
/**
* 关闭Statement和归还数据库连接Connection
* @param stmt
* @param conn
*/
public static void close(Statement stmt,Connection conn){
//关闭Statement
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//归还连接
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 关闭ResultSet,Statement和归还连接Connection
* @param rs
* @param stmt
* @param conn
*/
public static void close(ResultSet rs, Statement stmt, Connection conn) {
//关闭ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(stmt, conn);
}
/**
* 获取连接池
* @return
*/
public static DataSource getDataSource(){
return ds;
}
}
工具类写好之后,就可以开开心心的使用工具类来操作数据库了。
下面写一个insert语句来示范一下。
import cn.itcast.datasource.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class SqlPoolTest {
//使用数据库连接池完成一个添加记录的操作
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
//获取连接
conn = JDBCUtils.getConnection();
//定义sql语句
String sql = "insert into account values(null,?,?)";
//获得Statement对象
pstmt = conn.prepareStatement(sql);
pstmt.setString(1,"莫文蔚");
pstmt.setDouble(2,2341);
//执行sql
int count = pstmt.executeUpdate();
System.out.println(count);
} catch (SQLException e) {
e.printStackTrace();
}finally {
//关闭开启对象
JDBCUtils.close(pstmt,conn);
}
}
}
还有一个牛逼的工具:SpringJDBC
SpringJDBC提供了一个JdbcTemplate对象,用于简化开发。
为什么说简化呢?因为Spring对JDBC进行了封装,使用使只需要创建JdbcTemplate对象,然后就执行sql语句。不用去管创建连接和关闭连接的操作。
JdbcTemplate需要传入一个DataSource,之后就可以用JdbcTemplate对象进行CURD操作。
JdbcTemplate的常用方法:
1.update():执行增删改操作(DML)
2.queryForMap():将结果集封装为map集合,查询结果只能为一条
Map<String,Object> map = template.queryForMap(sql,1,2,3);//1,2,3是pstmt的?,?,?
3.queryForList():将结果集封装为list集合,将每一条结果封装为一个map,再将所有的map装载在一个list中
List<Map<String,Object>> list = template.queryForList(sql,1,2,3);//1,2,3是pstmt的?,?,?
4.query():将结果封装为JavaBean对象(常用)
//Emp是自定义的一个JavaBean类
List<Emp> list = template.query(sql,new RowMapper<Emp>(){
@override
public Emp mapRow(ResultSet rs,int i)throw SQLException{
Emp emp = new Emp();
emp.setId(rs.getInt("id"));
emp.setName(rs.getString("name"));
emp.setBalance(rs.getDouble("balance"))
return null;
}
});
//也可以使用Spring封装好的RowMapper类-->BeanPropertyRowMapper
List<Emp> list = template.query(sql,new BeanPropertyRowMapper<Emp>(Emp.class));
5.queryForObject():将结果封装为对象,一般用于聚合函数的查询封装。
String sql = "select count(id) from emp";
Long total = template.queryForObject(sql,Long.class);
下面说说咋用:
- 要用丝不润首先还是得导包。
commons-logging-1.2.jar
spring-beans-4.3.9.RELEASE.jar
spring-core-4.3.9.RELEASE.jar
spring-jdbc-4.3.9.RELEASE.jar
spring-tx-4.3.9.RELEASE.jar - 创建JdbcTemplate对象
- 执行sql
就是这么简单几步。
import cn.itcast.datasource.utils.JDBCUtils;
import org.springframework.jdbc.core.JdbcTemplate;
public class JdbcTemplateDemo1 {
public static void main(String[] args) {
//获得JdbcTemplate对象
JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
//编写sql语句
String sql = "update account set balance = ? where name = ? ";
//调用方法
int count = template.update(sql, 0, "莫文蔚");
System.out.println(count);
}
}