JDBC
: Java Database Connection java
数据库连接DDL
:数据库定义语言
DDQ
: 数据库查询语言(查询)
DDM
: 数据库管理语言(增删改)
sql
语句执行对象,执行sql
语句,释放资源。加载驱动①:
@Test
public void testGetDriver() throws ClassNotFoundException, SQLException {
Driver driver = new Driver();
//创建类交给DriverManager管理
DriverManager.registerDriverr(driver);
}
不推荐这种方式,加载驱动,因为在Driver源码中已经帮我们注册驱动了,使用这种方式就注册了两次驱动。当一个类被加载的时候,它的静态代码块就被执行了,源码如下:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
Class.forName("com.mysql.jdbc.Driver");//类被加载,执行静态代码块的注册驱动语句
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456");//获取连接
//简写模式
// Connection conn = DriverManager.getConnection("jdbc:mysql:///jdbc", "root", "123456");
DriverManager.getConnection(url,username,password)
提供了三个参数url
: 表示要连接的MYSQL
地址, localhost
表示连接的主机地址,3306表示端口号,后面跟的jdbc
表示连接的数据库名称。root
: 表示自己数据库的用户名称。password
: 连接数据库的密码。 //1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456");//获取连接
//3.获取语句执行对象
String sql = "select * from t_user";
PreparedStatement ps = conn.prepareStatement(sql);//预编译sql语句
sql
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456");//获取连接
//3.获取语句执行对象
String sql = "select * from t_user";
PreparedStatement ps = conn.prepareStatement(sql);//预编译sql语句
ResultSet resultSet = preparedStatement.executeQuery();//执行查询 返回结果集
resultSet.close();
ps.close();
conn.close;
SQL
注入问题如果使用Statement 获取sql
语句执行对象(Statement sm = conn.createStatement()
),sql
采用的是字符串拼接的形式,不安全,所以用PreparedStatement ps = conn.prepareStatement(sql);
得到语句执行对象,sql
中的问号(?)作为占位符。如下就可以查找所有用户。
SELECT * FROM tb_user WHERE user_name = '' OR 1=1 OR '' AND user_password = '1233' ;
select * from tb_user where 1=1 or ''='';
Statement
和PreparedStatement
的区别:Statement
:
①有sql
注入的风险;
②sql
是字符串拼接(sql
结构会根据参数的改变而改变);
③Statement
是PreparedStatement
的父类。
PreparedStatement
:
①可以防止sql
注入;
②sql
是预编译(sql
结构不会根据参数的改变而改变)的;
③PreparedStatement
是安全的;
④PreparedStatement
执行效率比Statement
高
概念:一组逻辑操作单元, 数据从一种状态改变为另一种状态。
原子性 : 把一次操作当成一个最小的单元不可以分割,要么同时成功要么同时失败回滚
一致性:一个事务执行的前后,数据的总量不会发生改变
隔离性:在并发的环境中,操作相同的数据是每一个事物都是独立的,相互不会影响
持久性:事物一旦提交,数据将永久保存到数据库中。
银行转账示例:
private static final Connection conn;
static {
conn = JDBCUtils.INSTANCE.getConnection();
}
PreparedStatement ps = null;
ResultSet res = null;
public void transferAccounts(String username, String username2, Double money) {
String sql1 = "update t_student set money = money - ? where username = ?";
try {
assert conn != null;
ps = conn.prepareStatement(sql1);
//将自动提交设为false,改为手动提交,保证事务的一致性
conn.setAutoCommit(false);
ps.setDouble(1, money);
ps.setString(2, username);
ps.executeUpdate();
// System.out.println(1/ 0);
//-----------------------------------
String sql2 = "update t_student set money = money + ? where username=?";
ps = conn.prepareStatement(sql2);
ps.setDouble(1, money);
ps.setString(2, username2);
ps.executeUpdate();
//自动提交,只有上面的代码都正常执行后才提交修改数据库数据,否则不能提交,数据库数据保持不变
conn.commit();
System.out.println("转账完成!");
} catch (Exception e) {
//如果有异常 事务回滚:将缓存的sql语句回滚出来,节约空间开销 将占用的数据释放掉
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
JDBCUtils.INSTANCE.close(ps, res, conn);
}
}
Statement.RETURN_GENERATED_KEYS
运行返回主键id可以使用
statement.getGeneratedKeys()
这个方法就是用来获取主键id的
//返回添加主键值
@Test
public void testGetKey() {
//底层执行了两次sql语句,还有一次是select max(id) from t_student;就得到了新增id值。
String sql = "insert into t_student(username, password, age, sex, money) VALUES (?,?,?,?,?)";
try {
//返回主键的sql语句
ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setObject(1, "青青");
ps.setObject(2, "123456");
ps.setObject(3, 18);
ps.setObject(4, 0);
ps.setObject(5, 900.0);
ps.executeUpdate();
res = ps.getGeneratedKeys();
res.next();//读取下一行数据
System.out.println("返回主键:" + res.getLong(1));
} catch (SQLException e) {
e.printStackTrace();
}
}
概述:数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。在Java中,连接池使用javax.sql.DataSource接口来表示连接池. 这里的DataSource就是连接池。连接池就是DataSource
常用连接池:
使用步骤:所需jarhttps://gitee.com/coderyeah/layui-module/tree/master/lib
步骤:
1. 导入jar包:HikariCP 3.2.0【版本根据需求】
2. 创建连接池参数HikariConfig对象
3. 连接池参数对象设置连接参数:
4. 创建连接池对象,将参数对象传入
5. 获取连接对象:getConnection();
# driverClassName驱动名【自定义即可,现在可以不写】
driverClassName=com.mysql.jdbc.Driver
# url连接ip和端口、数据库,以及字符集。都是项目中约定好的
url=jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=utf8
# username用户名【自定义即可】
username=root
# password密码【自定义即可】
password=root
#连接池启动时的初始值
initialSize=200
#连接池的最大值
maxActive=500
#连接池的最大空闲数
maxIdle=200
Hikari配置参数
CRUD综合案例
准备一个resource文件夹用来存放配置文件
username=root
password=123456
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///jdbc
准备工具类获取连接
package com.lqs.jdbc.utils;
import com.lqs.jdbc.dao.IStudentDao;
import com.lqs.jdbc.dao.impl.StudentDaoImpl;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.Test;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public enum JDBCUtils {
INSTANCE;
private static final Properties ps = new Properties();
// private static String driver;
//private static String url;
// private static String username;
// private static String password;
//private static HikariDataSource dataSource;
static {
try {
//读取配置文件 解决硬编码
ps.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
//创建连接池配置对象
HikariConfig config = new HikariConfig();
//设置配置参数
config.setUsername(ps.getProperty("username"));
config.setJdbcUrl(ps.getProperty("url"));
config.setDriverClassName(ps.getProperty("driver"));
config.setPassword(ps.getProperty("password"));
//创建数据源对象
dataSource = new HikariDataSource(config);
//获取连接
// dataSource.getConnection();
// driver = ps.getProperty("driver");
// username = ps.getProperty("username");
// password = ps.getProperty("password");
// url = ps.getProperty("url");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取连接
*
* @return
*/
public Connection getConnection() {
try {
/* Class.forName(driver);//加载驱动
final Connection conn = DriverManager.getConnection(url, username, password);
if (conn != null) {
return conn;
}*/
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 关闭资源
*/
public void close(PreparedStatement ps, ResultSet res, Connection conn) {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (res != null) {
try {
res.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
目录
Student类,生成get,set方法
接口中的方法
接口实现类
public class StudentDaoImpl implements IStudentDao {
private static final Connection conn;
static {
conn = JDBCUtils.INSTANCE.getConnection();
}
PreparedStatement ps = null;
ResultSet res = null;
@Override
public Student queryByUsernameAndPwd(String username, String pwd) {
final Connection conn = JDBCUtils.INSTANCE.getConnection();
String sql = "select * from t_student where username=? and password=?";
try {
assert conn != null;
ps = conn.prepareStatement(sql);
ps.setString(1, username);
ps.setString(2, pwd);
res = ps.executeQuery();
while (res.next()) {
final Student student = new Student();
student.setAge(res.getInt("age"));
student.setUsername(res.getString("username"));
student.setIntro(res.getString("intro"));
student.setSex(res.getBoolean("sex"));
student.setId(res.getLong("id"));
student.setPassword(res.getString("password"));
return student;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.INSTANCE.close(ps, res, conn);
}
return null;
}
@Override
public boolean add(Student student) {
String sql = "insert into t_student(username,password,sex,age,intro) values(?,?,?,?,?)";
if (conn != null) {
try {
ps = conn.prepareStatement(sql);//预编译sql语句
ps.setString(1, student.getUsername());
ps.setString(2, student.getPassword());
ps.setBoolean(3, student.isSex());
ps.setInt(4, student.getAge());
ps.setString(5, student.getIntro());
final int i = ps.executeUpdate();
if (i == 1) {
return true;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.INSTANCE.close(ps, res, conn);
}
} else {
System.out.println("获取连接失败!!!");
}
return false;
}
@Override
public boolean del(Long id) {
String sql = "delete from t_student where id=?";
if (conn != null) {
try {
ps = conn.prepareStatement(sql);//预编译sql语句
ps.setLong(1, id);
final int i = ps.executeUpdate();
if (i == 1) {
return true;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.INSTANCE.close(ps, res, conn);
}
} else {
System.out.println("获取连接失败!!!");
}
return false;
}
@Override
public boolean update(Student student, Long id) {
String sql = "update t_student set username =?, password=?, sex=?, age=?, intro=? where id = ?";
if (conn != null) {
try {
ps = conn.prepareStatement(sql);
ps.setString(1, student.getUsername());
ps.setString(2, student.getPassword());
ps.setBoolean(3, student.isSex());
ps.setInt(4, student.getAge());
ps.setString(5, student.getIntro());
ps.setLong(6, id);
final int i = ps.executeUpdate();
if (i == 1) {
return true;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.INSTANCE.close(ps, res, conn);
}
} else {
System.out.println("获取连接失败!!!");
}
return false;
}
@Override
public Student queryById(Long id) {
String sql = "select * from t_student where id=?";
try {
if (conn != null) {
ps = conn.prepareStatement(sql);
ps.setLong(1, id);
res = ps.executeQuery();
while (res.next()) {
final Student student = new Student();
student.setId(res.getLong("id"));
student.setUsername(res.getString("username"));
student.setAge(res.getInt("age"));
student.setSex(res.getBoolean("sex"));
student.setIntro(res.getString("intro"));
student.setPassword(res.getString("password"));
return student;
}
} else {
System.out.println("获取连接失败!!!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.INSTANCE.close(ps, res, conn);
}
return null;
}
@Override
public List<Student> queryAll() {
String sql = "select * from t_student";
try {
if (conn != null) {
ps = conn.prepareStatement(sql);
res = ps.executeQuery();
List<Student> students = new ArrayList<>();
while (res.next()) {
Student student = new Student();
student.setId(res.getLong("id"));
student.setUsername(res.getString("username"));
student.setAge(res.getInt("age"));
student.setSex(res.getBoolean("sex"));
student.setIntro(res.getString("intro"));
student.setPassword(res.getString("password"));
students.add(student);
}
return students;
} else {
System.out.println("获取连接失败!!!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.INSTANCE.close(ps, res, conn);
}
return null;
}
@Override
public void transferAccounts(String username, String username2, Double money) {
String sql1 = "update t_student set money = money - ? where username = ?";
try {
assert conn != null;
ps = conn.prepareStatement(sql1);
//将自动提交设为false,改为手动提交,保证事务的一致性
conn.setAutoCommit(false);
ps.setDouble(1, money);
ps.setString(2, username);
ps.executeUpdate();
// System.out.println(1/ 0);
//-----------------------------------
String sql2 = "update t_student set money = money + ? where username=?";
ps = conn.prepareStatement(sql2);
ps.setDouble(1, money);
ps.setString(2, username2);
ps.executeUpdate();
//自动提交,只有上面的代码都正常执行后才提交修改数据库数据,否则不能提交,数据库数据保持不变
conn.commit();
System.out.println("转账完成!");
} catch (Exception e) {
//如果有异常 事务回滚:将缓存的sql语句回滚出来,节约空间开销 将占用的数据释放掉
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
JDBCUtils.INSTANCE.close(ps, res, conn);
}
}
}
PrepareStatement
和Statement
的区别:1.prepareStatement会先初始化SQL,先把这个SQL提交到数据库中进行预处理,多次使用可提高效率,不用一直更改SQL语句,只需要修改变量就行了,还可以有效的防止SQL注入攻击。
2.使用 Statement 对象。在对数据库只执行一次性存取,PreparedStatement 对象的开销比Statement大,对于一次性操作并不会带来额外的好处。
3.preparedstatement是预编译得, preparedstatement支持批处理
一段Java代码操作数据库,需要取得连接,每次操作数据库都是需要取到一个连接。
例如:新浪首页,查询体育新闻,需要一条sql,查询娱乐新闻,需要一条sql,可能一个首页面,就会存在100多个请求(到数据库查询),那这样浪费多少秒? 浪费1000秒
结论:会浪费很多时间
每次请求都会创建一个connection,因此会浪费资源(内存),当同时1000人访问的时候,那就会占用很多资源,因此很浪费时间和容器操作系统崩溃;
所以,我们用连接池来提供资源利用率。
/**
* 封装一个通用的增删改
*
* @param sql sql语句
* @param obj 参数数组
*/
public void executeUpdate(String sql, Object... obj) {
Connection conn = null;
PreparedStatement ps = null;
try {
//获取连接
conn = dataSource.getConnection();
//获取语句执行对象
ps = conn.prepareStatement(sql);
// 给sql赋值
for (int i = 0; i < obj.length; i++) {
ps.setObject(i + 1, obj[i]);
}
//执行sql
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(ps, null, conn);
}
}
ResultSetMetaData |
getMetaData() 检索此 ResultSet 对象的列的数量,类型和属性。 |
---|---|
int |
getColumnCount() 返回此 ResultSet 对象中的列数。 |
String |
getColumnLabel(int column) 获取指定列的建议标题用于打印输出和显示。 |
public <T> T executeQueryOne(String sql, Class<T> clazz, Object... obj) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet res = null;
try {
//通过连接池获取连接对象
conn = dataSource.getConnection();
//获取执行对象
ps = conn.prepareStatement(sql);
//给占位符赋值
for (int i = 0; i < obj.length; i++) {
ps.setObject(i + 1, obj[i]);
}
//得到结果集
res = ps.executeQuery();
//获取结果集的元数据ResultSetMetaData getMetaData()
//检索此 ResultSet对象的列的数量,类型和属性。
ResultSetMetaData metaData = res.getMetaData();
// 返回此 `res`对象中的列数。
int columnCount = metaData.getColumnCount();
//判断结果集中是否存在下一条数据
if (res.next()) {
//通过字节码对象 反射出对象实例
final T t = clazz.newInstance();//必须有一个无参构造器
for (int i = 0; i < columnCount; i++) {
//获取别名
String columnLabel = metaData.getColumnLabel(i + 1);
System.out.println("columnLabel==" + columnLabel);
//获取查询的一个子段值
final Object objVal = res.getObject(i + 1);
System.out.println("objVal==" + objVal);
//通过反射封装字段
final Field field = clazz.getDeclaredField(columnLabel);//注意数据库字段要和实体类中保持一致
field.setAccessible(true);//允许访问private 修饰的字段
field.set(t, objVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
close(ps, res, conn);
}
return null;
}
查询集合:与上面类似,将if(res.next())换为while(res.next()),再放入集合中即可
/**
* @param sql sql语句
* @param clazz 字节码对象
* @param obj 参数数组
* @param 定义泛型
* @return 集合
*/
public <T> List<T> queryMany(String sql, Class<T> clazz, Object... obj) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet res = null;
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement(sql);
//给占位符赋值
for (int i = 0; i < obj.length; i++) {
ps.setObject(i + 1, obj[i]);
}
res = ps.executeQuery();
//获取元数据
final ResultSetMetaData metaData = res.getMetaData();
final int columnCount = metaData.getColumnCount();//得到列数
//创建List集合对象用于保存数据
List<T> tList = new ArrayList<>();
while (res.next()) {
final T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {//循环每一列
final String columnLabel = metaData.getColumnLabel(i + 1);//数据库中字段名
final Object objVal = res.getObject(i + 1);//数据库中字段名对应的值
final Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, objVal);
}
tList.add(t);
}
return tList;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}