JDBC (Java Database Connectivity )指使用Java连接数据库,对数据库实现CRUD操作。
Java定义一套操作数据库的规范(接口),各数据库厂商去实现该接口,保证程序员在使用上的统一。
API(类和接口):在java.sql包中
- DriverManager类:获得数据库连接
- Connection接口:连接的接口
- Statement接口以及其子接口PreparedStatment接口:操作数据库语句
- ResultSet接口:查询时返回的结果集
- SQLException类:数据库操作过程中的异常处理
驱动:
- ODBC:桥接,使用系统去连接数据库,能够通用的连接大部分数据库,但是性能不好,而且功能不完整。
- JDBC:数据库厂商提供的直连驱动包。
- mysql-connector-java-5.1.40.jar:连接MySQL5的驱动
- mysql-connector-java-8.0.20.jar:连接MySQL8的驱动
操作步骤:
0、将驱动包导入
1、加载驱动
2、获得连接
3、通过连接获得SQL的执行对象
4、通过执行对象执行SQL
5、处理执行结果
6、释放资源
public class JobsDAO {
public static void main(String[] args) {
// 1、加载驱动
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
final String url = "jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8";
final String username = "root";
final String password = "root";
try (
// 2、获得连接
final Connection connection = DriverManager.getConnection(url, username, password);
// 3、获取执行sql对象
final Statement statement = connection.createStatement()
){
System.out.println(connection);
// final String sql = "INSERT INTO t_jobs VALUES ('2', 'BBB', 2000, 10000);";
// // 4、执行SQL语句(更新操作,增删改),返回影响的行数
// final int count = statement.executeUpdate(sql);
// // 5、处理结果
// if (count > 0){
// System.out.println("执行成功");
// }else{
// System.out.println("执行失败");
// }
final String sql = "SELECT COUNT(1) FROM t_jobs";
// 4、执行SQL语句(查询操作),返回一个多行多列的结果集
final ResultSet resultSet = statement.executeQuery(sql);
// 5、处理结果
// 循环每一行
while (resultSet.next()){
// 获取当前行每一列的值
// 可以通过列的序号来获取,也可以通过列名来获取,推荐使用名称,COUNT(1)这种名称不太好使用的,推荐使用别名
final long count = resultSet.getLong(1);
System.out.println(count);
}
}catch (SQLException e){
e.printStackTrace();
}
}
}
用来封装查询结果集。
注意:ResultSet是仅向前操作的对象,通过next方法向前操作,不能回退。
当移动到当前行时,使用getXxx()方法来获得某个列的值,方法的参数为列名或者序号(从1开始)。
public class JobsDAO {
public static void main(String[] args) {
// 1、加载驱动
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
final String url = "jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8";
final String username = "root";
final String password = "root";
try (
// 2、获得连接
final Connection connection = DriverManager.getConnection(url, username, password);
// 3、获取执行sql对象
final Statement statement = connection.createStatement()
){
System.out.println(connection);
final String sql = "SELECT COUNT(1) FROM t_jobs";
// 4、执行SQL语句(查询操作),返回一个多行多列的结果集
final ResultSet resultSet = statement.executeQuery(sql);
// 5、处理结果
// 循环每一行
while (resultSet.next()){
// 获取当前行每一列的值
// 可以通过列的序号来获取,也可以通过列名来获取,推荐使用名称,COUNT(1)这种名称不太好使用的,推荐使用别名
final long count = resultSet.getLong(1);
System.out.println(count);
}
}catch (SQLException e){
e.printStackTrace();
}
}
}
利用拼接SQL字符串中的单引号,来注入关键字来实现非法操作。
例如:一个简单登录操作如下:
System.out.println(connection);
String u = "zhangsan";
String p = "123456";
final String sql = "SELECT COUNT(1) FROM stu WHERE username = '"+u+"' AND pwd = '"+p+"'";
// 4、执行SQL语句(查询操作),返回一个多行多列的结果集
final ResultSet resultSet = statement.executeQuery(sql);
// 5、处理结果
// 循环每一行
while (resultSet.next()){
// 获取当前行每一列的值
// 可以通过列的序号来获取,也可以通过列名来获取,推荐使用名称,COUNT(1)这种名称不太好使用的,推荐使用别名
final long count = resultSet.getLong(1);
System.out.println(count > 0);
}
上面的代码执行看似没有问题,但是当用户将密码输入
sdadsd' OR '1' = '1
,无论用户名是什么,都会显示登录成功。就是简单的SQL注入攻击。
PreparedStatement接口是Statement接口的子接口。
特点:
- 预编译语句,效率高
- 安全,避免SQL注入
- 动态填充内容,可以重复使用
// ?表示占位符
final String sql = "INSERT INTO stu(name, username, pwd) VALUES (?, ?, ?);";
try (
// 2、获得连接
final Connection connection = DriverManager.getConnection(url, username, password);
// 3、获取执行sql对象
final PreparedStatement preparedStatement = connection.prepareStatement(sql)
){
for (int i = 0; i < 5; i++) {
// 设置参数
preparedStatement.setString(1, "AAA" + i);
preparedStatement.setString(2, "BBBB" + i);
preparedStatement.setString(3, "CCCC" + i);
// 4、执行SQL语句(更新操作,增删改),返回影响的行数
final int count = preparedStatement.executeUpdate();
// 5、处理结果
if (count > 0){
System.out.println("执行成功");
}else{
System.out.println("执行失败");
}
}
}catch (SQLException e){
e.printStackTrace();
}
封装连接类:
public class DBConnection {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8",
"root", "root");
}
}
上面的内容,无法热修改,因为需要修改源代码,所以对于可能在上线后还需要修改的参数,应该使用配置文件。如下处理:
dbconfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf-8
jdbc.user=root
jdbc.password=root
private final static Properties PROPERTIES = new Properties();
static {
try {
// 读取并加载配置文件
final InputStream inputStream = DBConnection1.class.getResourceAsStream("/dbconfig.properites");
PROPERTIES.load(inputStream);
Class.forName(PROPERTIES.getProperty("jdbc.driver"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(PROPERTIES.getProperty("jdbc.url"),
PROPERTIES.getProperty("jdbc.user"), PROPERTIES.getProperty("jdbc.password"));
}
}
ORM:Object Relational Mapping对象关系映射。
是指将实体类的对象与数据库中的一行记录相对应。
DAO:Data Access Object数据访问对象,即数据库操作类的对象。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
private String username;
private String pwd;
}
public interface StudentDAO {
/**
* 添加
* @param student
* @return
*/
boolean save(Student student);
/**
* 删除
* @param id
* @return
*/
boolean delete(int id);
/**
* 修改
* @param student
* @return
*/
boolean update(Student student);
/**
* 根据id查询
* @param id
* @return
*/
Student findById(int id);
/**
* 查询所有
* @return
*/
List<Student> findAll();
}
public interface Constants {
String SQL_STUDENT_SAVE = "INSERT INTO stu(name, username, pwd) VALUES (?,?,?)";
String SQL_STUDENT_UPDATE = "UPDATE stu SET name = ?, username = ?, pwd = ? WHERE id = ?";
String SQL_STUDENT_DELETE = "DELETE FROM stu WHERE id = ?";
String SQL_STUDENT_FIND_BY_ID = "SELECT id, name, username, pwd FROM stu WHERE id = ?";
String SQL_STUDENT_FIND_ALL = "SELECT id, name, username, pwd FROM stu";
}
public class StudentDAOImpl implements StudentDAO {
@Override
public boolean save(Student student) {
try(
Connection connection = DBConnection.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_STUDENT_SAVE);
) {
// 设置参数
preparedStatement.setString(1, student.getName());
preparedStatement.setString(2, student.getUsername());
preparedStatement.setString(3, student.getPwd());
return preparedStatement.executeUpdate() > 0;
}catch (SQLException e){
e.printStackTrace();
}
return false;
}
@Override
public boolean delete(int id) {
try(
Connection connection = DBConnection.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_STUDENT_DELETE);
) {
// 设置参数
preparedStatement.setInt(1, id);
return preparedStatement.executeUpdate() > 0;
}catch (SQLException e){
e.printStackTrace();
}
return false;
}
@Override
public boolean update(Student student) {
try(
Connection connection = DBConnection.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_STUDENT_UPDATE);
) {
// 设置参数
preparedStatement.setString(1, student.getName());
preparedStatement.setString(2, student.getUsername());
preparedStatement.setString(3, student.getPwd());
preparedStatement.setInt(4, student.getId());
return preparedStatement.executeUpdate() > 0;
}catch (SQLException e){
e.printStackTrace();
}
return false;
}
@Override
public Student findById(int id) {
try(
Connection connection = DBConnection.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_STUDENT_FIND_BY_ID);
) {
// 设置参数
preparedStatement.setInt(1, id);
final ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
return new Student(
resultSet.getInt("id"),
resultSet.getString("name"),
resultSet.getString("username"),
resultSet.getString("pwd")
);
}
}catch (SQLException e){
e.printStackTrace();
}
return null;
}
@Override
public List<Student> findAll() {
List<Student> list = new ArrayList<>();
try(
Connection connection = DBConnection.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_STUDENT_FIND_ALL);
) {
final ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
final Student student = new Student(
resultSet.getInt("id"),
resultSet.getString("name"),
resultSet.getString("username"),
resultSet.getString("pwd")
);
list.add(student);
}
}catch (SQLException e){
e.printStackTrace();
}
return list;
}
}
业务逻辑层,也写作Business(简写Biz)。
代表软件中的功能。
作用:一般情况下,一个业务就是一个方法。在业务中调用数据库操作。
- 界面层View
- 业务层Service
- 数据访问层DAO
调用顺序:View -> Service -> DAO
作用:需要修改时,只需要考虑其中某一层。
高内聚,低耦合:将代码中相似或相同的内容,抽取出相似的部分,写到一起,其他的对他进行调用,称为高内聚。低耦合,是指降低代码中相互的依赖性。
包的名称方式:
- entity:存放实体类
- dao:数据库操作
- service:业务逻辑
- view:界面
- util:帮助类
案例:学生信息管理系统
步骤:
1、connection.setAutoCommit(false);
2、connection.commit();在执行最后执行。
3、connection.rollback()在代码异常时执行。
注意:
- 如果在DAO中获取了连接,然后在Service中也获取连接,那么在多个连接中无法实现事务处理。
要想实现事务处理,必须在同一个连接中。为了实现同一个连接,有以下几种处理方式:
- 1、将连接作为参数传递,可以实现,但是会导致代码的侵入性。如果service调用service还是多个连接。会出错。
- 2、将连接存在一个公共位置,谁用谁取。看似可行,其实有线程安全问题,而且多线程操作名字相同会覆盖。
- 为了解决上面2的问题,又要实现1的不足(耦合度高),系统提供一个类ThreadLocal。
每个线程通过ThreadLocal绑定一个map对象。
当前线程中的所有流程步骤都共享该map对象、
// ThreadLocal源码
// 通过当前线程绑定的map设置值
public void set(T value) {
// 获得当前线程
Thread t = Thread.currentThread();
// 根据当前线程获得该线程绑定的map
ThreadLocalMap map = getMap(t);
if (map != null)
// 将对象设置到当前map中,key为当前ThreadLocal对象
map.set(this, value);
else
// 如果没有,则通过该线程创建一个map
createMap(t, value);
}
// 通过当前线程绑定的map获取值
public T get() {
// 获得当前线程
Thread t = Thread.currentThread();
// 根据当前线程获得该线程绑定的map
ThreadLocalMap map = getMap(t);
if (map != null) {
// 通过key为当前ThreadLocal对象来获取存储的对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 返回该对象
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public class DBConnection {
private static final Properties PROPERTIES = new Properties();
private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();
static {
final InputStream inputStream = DBConnection.class.getResourceAsStream("/dbconfig.properties");
try {
PROPERTIES.load(inputStream);
Class.forName(PROPERTIES.getProperty("jdbc.driver"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
// 在当前线程绑定的map中获取连接
Connection connection = THREAD_LOCAL.get();
if (connection == null){
// 创建连接
connection = DriverManager.getConnection(PROPERTIES.getProperty("jdbc.url"),
PROPERTIES.getProperty("jdbc.user"),
PROPERTIES.getProperty("jdbc.password"));
// 设置到当前线程绑定的map中
THREAD_LOCAL.set(connection);
}
return connection;
}
public static void closeAll(){
// 在当前线程绑定的map中获取连接
Connection connection = THREAD_LOCAL.get();
if (connection != null){
try {
connection.close();
// 在当前线程绑定的map中删除连接
THREAD_LOCAL.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
public class AccountServiceImpl implements AccountService {
private AccountDAO accountDAO = new AccountDAOImpl();
@Override
public void transfer(Account from, Account to) {
Connection connection = null;
try{
connection = DBConnection.getConnection();
connection.setAutoCommit(false);
accountDAO.decrease(from);
// int i = 5 / 0;
accountDAO.increase(to);
connection.commit();
}catch (Exception e){
e.printStackTrace();
if (connection != null){
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}finally {
DBConnection.closeAll();
}
}
}
public class AccountDAOImpl implements AccountDAO {
@Override
public boolean increase(Account account) throws SQLException {
Connection connection = DBConnection.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_ACCOUNT_INCREASE);
preparedStatement.setInt(1, account.getMoney());
preparedStatement.setString(2, account.getAccount());
return preparedStatement.executeUpdate() > 0;
}
@Override
public boolean decrease(Account account) throws SQLException {
Connection connection = DBConnection.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(Constants.SQL_ACCOUNT_DECREASE);
preparedStatement.setInt(1, account.getMoney());
preparedStatement.setString(2, account.getAccount());
return preparedStatement.executeUpdate() > 0;
}
}
public interface RowMapper<T> {
T getMapper(ResultSet resultSet) throws SQLException;
}
public class DAOUtils<T> {
public boolean update(String sql, Object... args) throws SQLException {
// 获得连接
final Connection connection = DBConnection.getConnection();
// 获得sql预编译操作对象
final PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
// 执行
return preparedStatement.executeUpdate() > 0;
}
public List<T> queryList(String sql, RowMapper<T> mapper, Object... args) throws SQLException {
List<T> list = new ArrayList<>();
final Connection connection = DBConnection.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
final ResultSet resultSet = preparedStatement.executeQuery();
// 循环结果集
while (resultSet.next()){
// 封装结果集的行
T t = mapper.getMapper(resultSet);
// 添加到集合中
list.add(t);
}
return list;
}
public T queryOne(String sql, RowMapper<T> mapper, Object... args) throws SQLException {
final Connection connection = DBConnection.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
final ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
T t = mapper.getMapper(resultSet);
return t;
}
return null;
}
}
public class StudentDAO {
private DAOUtils<Student> daoUtils = new DAOUtils();
public boolean save(Student student) throws SQLException {
return daoUtils.update(Constants.SQL_STUDENT_SAVE,
student.getName(), student.getSex(),
student.getAge(), student.getPhone());
}
public boolean update(Student student) throws SQLException {
return daoUtils.update(Constants.SQL_STUDENT_UPDATE,
student.getName(), student.getSex(),
student.getAge(), student.getPhone(), student.getId());
}
public boolean delete(int id) throws SQLException {
return daoUtils.update(Constants.SQL_STUDENT_DELETE, id);
}
public List<Student> findAll() throws SQLException{
return daoUtils.queryList(Constants.SQL_STUDENT_FIND_ALL, new RowMapper<Student>() {
@Override
public Student getMapper(ResultSet resultSet) throws SQLException {
return new Student(
resultSet.getInt("id"),
resultSet.getString("name"),
resultSet.getString("sex"),
resultSet.getInt("age"),
resultSet.getString("phone")
);
}
});
}
public Student findById(int id) throws SQLException{
return daoUtils.queryOne(Constants.SQL_STUDENT_FIND_BY_ID, resultSet -> new Student(
resultSet.getInt("id"),
resultSet.getString("name"),
resultSet.getString("sex"),
resultSet.getInt("age"),
resultSet.getString("phone")
)
, id);
}
}
避免频繁创建和关闭连接。
连接池有很多种:DBCP、C3P0、druid(阿里出品)
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/stu_sys?useUnicode=true&characterEncoding=utf-8
username=root
password=root
#初始化连接数
initialSize=10
#最大活动连接数
maxActive=50
#最小空闲连接
minIdle=5
#最大等待时间5秒
maxWait=5000
上面的配置信息,key不能改,采用约定优于配置原则
约定优于配置:事先约定好key的名称,如果遵守约定,那么就无需配置,如果不遵守约定,就需要添加额外的配置。
public class DBConnection {
private static final Properties PROPERTIES = new Properties();
private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();
private static DataSource dataSource; // 数据源连接池
static {
final InputStream inputStream = DBConnection.class.getResourceAsStream("/druid.properties");
try {
PROPERTIES.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(PROPERTIES);
} catch (Exception e) {
e.printStackTrace();
}
}
public static DataSource getDataSource(){
return dataSource;
}
// dbutils类只有需要事务时才使用
public static Connection getConnection() throws SQLException {
Connection connection = THREAD_LOCAL.get();
if (connection == null){
connection = dataSource.getConnection();
THREAD_LOCAL.set(connection);
}
return connection;
}
public static void closeAll() throws SQLException {
Connection connection = THREAD_LOCAL.get();
if (connection != null){
// 在连接池中获取的连接,关闭时会还到池中再次使用
connection.close();
THREAD_LOCAL.remove();
}
}
}
当没有事务时,可以直接使用:
private QueryRunner runner = new QueryRunner(DBConnection.getDataSource());
注意:结果集的封装,使用反射的方式需要字段名和属性名一致。
public class StudentDAOImpl implements StudentDAO {
// DBUtils中的操作类的对象
// 当没有事务时,直接使用连接池对象
private QueryRunner runner = new QueryRunner(DBConnection.getDataSource());
@Override
public boolean save(Student student) throws SQLException {
return runner.update(Constants.SQL_STUDENT_SAVE, student.getName(), student.getSex(),
student.getAge(), student.getPhone()) > 0;
}
@Override
public boolean update(Student student) throws SQLException {
return runner.update(Constants.SQL_STUDENT_UPDATE, student.getName(), student.getSex(),
student.getAge(), student.getPhone(), student.getId()) > 0;
}
@Override
public boolean delete(int id) throws SQLException {
return runner.update(Constants.SQL_STUDENT_DELETE, id) > 0;
}
// @Override
// public List findAll() throws SQLException {
// // 通过反射封装结果集,但是需要字段名与属性名一致
// return runner.query(Constants.SQL_STUDENT_FIND_ALL, new BeanListHandler<>(Student.class));
// }
// 当字段名与属性不一致时,可以使用以下办法:
// 1、当个别属性不一致时,可以通过别名的方式
// 2、当很多属性不一致时,可以自己手动封装结果集
@Override
public List<Student> findAll() throws SQLException {
// 通过反射封装结果集,但是需要字段名与属性名一致
return runner.query(Constants.SQL_STUDENT_FIND_ALL, new ResultSetHandler<List<Student>>() {
@Override
public List<Student> handle(ResultSet resultSet) throws SQLException {
List<Student> list = new ArrayList<>();
while (resultSet.next()){
Student student = new Student(
resultSet.getInt("id"),
resultSet.getString("name1"),
resultSet.getString("sex"),
resultSet.getInt("age"),
resultSet.getString("phone")
);
list.add(student);
}
return list;
}
});
}
@Override
public Student findById(int id) throws SQLException {
return runner.query(Constants.SQL_STUDENT_FIND_BY_ID, new BeanHandler<>(Student.class), id);
}
}
当有事务时:
public class AccountDAOImpl implements AccountDAO {
private QueryRunner runner = new QueryRunner();
@Override
public boolean increase(Account account) throws SQLException {
return runner.update(DBConnection.getConnection(),
Constants.SQL_ACCOUNT_INCREASE,
account.getMoney(), account.getAccount()) > 0;
}
@Override
public boolean decrease(Account account) throws SQLException {
return runner.update(DBConnection.getConnection(),
Constants.SQL_ACCOUNT_DECREASE,
account.getMoney(), account.getAccount()) > 0;
}
}
public class AccountServiceImpl implements AccountService {
private AccountDAO accountDAO = new AccountDAOImpl();
@Override
public void transfer(Account from, Account to) throws SQLException {
Connection connection = null;
try{
connection = DBConnection.getConnection();
connection.setAutoCommit(false);
accountDAO.decrease(from);
int i = 5 / 0;
accountDAO.increase(to);
connection.commit();
}catch (Exception e){
e.printStackTrace();
if (connection != null){
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}finally {
DBConnection.closeAll();
}
}
}