背景:主机正在运行mysql服务
在cmd输入
mysql -u root -p
之后,输入密码(我的用户名是root,密码是root),成功登录到mysql。
输入:SHOW GLOBAL VARIABLES LIKE 'port'; 检查mysql服务的端口号
得到我的mysql服务的主机ip是localhost,端口号是3306,用户名是root,密码是root
Java操作数据库的方式:
1. JDBC
1.1 JDBC介绍
JDBC就是通过Java程序操作数据库。
- JDBC:Java DataBase Connectivity。意思为Java数据库连接
- JDBC是Java提供的一组独立于任何数据库管理系统的API
- JDBC提供接口规范,由各个数据库厂商提供接口的实现,厂商提供的实现类封装成jar文件,也就是我们的数据库驱动jar包。
- 学习JDBC,充分体现了面向接口编程的好处,程序员只关心标准和规范,而无需关注实现过程。
1.2 JDBC的核心组成
- 接口规范(SUN公司提供):
- 为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。
- 实现规范(数据库厂商提供jar包):
- 因为各个数据库厂商的DBMS软件各有不同,那么各自的内部如何通过SQL实现CRUD,只有数据库厂商清楚,因此把接口规范的实现交给各个数据库厂商自己实现。
- 数据库厂商将实现的内容和过程封装成jar文件,我们程序员只需要将jar文件引入到项目中集成即可,就可以开发调用过程操作数据库了
例如:
Mysql数据库的jar包:mysql-connetor-jdbc.jar
SQLite数据库的jar包:sqlite-jdbc.jar
Oracle数据库的jar包:ojdbc8.jar
1.3 JDBC快速入门
(1)在数据库中添加数据
CREATE DATABASE atguigu;
USE atguigu;
CREATE TABLE t_emp(
emp_id INT AUTO_INCREMENT COMMENT '员工编号' PRIMARY KEY,
emp_name VARCHAR(100) NOT NULL COMMENT '员工姓名',
emp_salary DOUBLE(10,5) NOT NULL COMMENT '员工薪资',
emp_age INT NOT NULL COMMENT '员工年龄'
);
INSERT INTO t_emp (emp_name, emp_salary, emp_age)
VALUES ('andy',777.77,32),
('大风哥', 666.66, 41),
('康师傅',111,23),
('Gavin',123,26),
('小鱼儿',123,28);
SELECT * FROM t_emp;
(2)导入mysql-connector-java的jar包
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
(3)编写代码
package com.gtc.base;
import java.sql.*;
public class JdbcDemo01 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1. 注册驱动 (注册mysql数据库的驱动)
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接对象 (java程序连接mysql)
String url = "jdbc:mysql://localhost:3306/atguigu";
String username = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url,username,password);
//3. 获取执行SQL语句的对象
Statement statement = connection.createStatement();
//4. 编写SQL语句
String sql = "select * from t_emp";
//5. 执行SQL语句(返回结果集)
ResultSet resultSet = statement.executeQuery(sql);
//6. 处理结果,遍历resultSet结果集
while (resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
String empSalary = resultSet.getString("emp_salary");
String emp_age = resultSet.getString("emp_age");
System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+emp_age);
}
// 7. 释放三个资源
resultSet.close();
statement.close();
connection.close();
}
}
得到结果:
1.4 JDBC的核心API
(1)注册驱动Driver
Drive类位于mysql-connector-java包下的com.mysql.jdbc.Driver。其实现了SUN公司提供的Java接口。
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!");
}
}
}
JDK6之后,可以不写注册驱动。
(2)Connection
- Connection接口时JDBC API的重要接口,用于建立与数据库的通信通道。换而言之,Connection对象不为空,则代表一次数据库连接。
- 在建立连接时,需要指定数据库URL、用户名、密码参数
- Connection接口还负责管理事务,Connection接口提供了commit和rollback方法,用于提交事务和回滚事务。
- 可以创建Statement对象,用于执行SQL语句并与数据库进行交互。在使用JDBC技术时,必须要先获取Connection对象,在使用完毕后,要释放资源,避免资源占用浪费及泄露。
(3)Statement
Statment用于执行SQL语句并与数据库进行交互。它是JDBC API中的一个重要接口,通过Statement对象,可以向数据库发送SQL语句并获取结果。
Statement接口在执行SQL语句时,会存在SQL注入的问题
SQL注入问题就是,我们在指定条件查询时加入“1=1”这样的sql语句,能够查询到所有记录。
例如:
select emp_id, emp_name form t_emp where emp_name='aaa' or '1' = '1';
此时,后面的or 1=1会导致,无论姓名是否存在,都可以查询到所有记录。
(4)PreparedStatement
- PreparedStatement是Statement接口的子接口,用于执行预编译的SQL查询,作用如下:
- 预编译SQL语句:在创建PreparedStatement时,就会预编译SQL语句,也就是SQL语句已经固定。
- 防止SQL注入:PreparedStatement支持参数化查询,将数据作为参数传递到SQL语句中,采用?占位符的方式,将传入的参数用一对单引号包裹起来,无论传递什么作为值。有效防止传入关键字或值导致的SQL注入问题
(5)ResultSet
- Result是JDBC API中的一个接口,用于表示从数据库中执行查询语句所返回的结果集
- 遍历结果:ResultSet可以使用next()方法将游标移动到结果集的下一行,逐行遍历数据库查询的结果,返回值为boolean类型,true代表有下一行结果,false则代表没有
- 获取单列结果:可以通过getXxx的方法获取单列的数据,该方法为重载方法,支持索引和列名进行获取。
1.5 PreparedStatement实现CRUD
查询Retrieve:
public class JdbcOperation {
/**
* 查询单行单列数据
* @throws SQLException
*/
@Test
public void testQuerySingleRowAndCol() throws SQLException {
// 1. 注册驱动(可以省略)
// 2. 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
// 3. 预编译SQL语句,得到PreparedStatement
PreparedStatement preparedStatement = connection.prepareStatement("select count(*) as count from t_emp");
// 4. 执行SQL语句,获取结果
ResultSet resultSet = preparedStatement.executeQuery();
// 5. 处理结果
while (resultSet.next()) {
int count = resultSet.getInt("count");
System.out.println(count);
}
// 6. 释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
/**
* 查询单行多列
* @throws SQLException
*/
@Test
public void testQuerySingleRow() throws SQLException {
// 1. 注册驱动
// 2. 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
// 3. 预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement("select emp_id, emp_name, emp_salary, emp_age from t_emp where emp_id=?");
// 4. 为占位符?赋值, 并执行SQL语句
preparedStatement.setInt(1,5);
ResultSet resultSet = preparedStatement.executeQuery();
// 5. 处理结果
while (resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+empAge);
}
// 6. 释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
/**
* 查询多行多列
* @throws SQLException
*/
@Test
public void testQueryMoreRow() throws SQLException {
// 1. 注册驱动
// 2. 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
// 3. 预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement("select emp_id, emp_name, emp_salary, emp_age from t_emp where emp_age > 25");
// 4. 执行SQL语句
ResultSet resultSet = preparedStatement.executeQuery();
// 5. 处理结果集(一次循环处理一行)
while (resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+empAge);
}
// 6. 释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
增加Create:
@Test
public void testQueryAdd() throws SQLException {
// 1. 注册驱动
// 2. 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
// 3. 预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement("insert into t_emp(emp_name, emp_salary,emp_age) values(?,?,?)");
// 4. 为占位符赋值, 并执行SQL
preparedStatement.setString(1,"rose");
preparedStatement.setDouble(2,345.67);
preparedStatement.setInt(3,45);
int result = preparedStatement.executeUpdate(); // 执行新增SQL
// 根据受影响行数,判断是否新增成功
if (result > 0){
System.out.println("success");
}else {
System.out.println("fail");
}
// 5. 释放资源
preparedStatement.close();
connection.close();
}
修改Update:
@Test
public void testUpdate() throws SQLException {
// 1. 注册驱动
// 2. 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
// 3. 预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement("update t_emp set emp_salary=? where emp_id=?");
// 4. 占位符赋值
preparedStatement.setDouble(1,888.88);
preparedStatement.setInt(2,6);
int result = preparedStatement.executeUpdate();
// 根据受影响行数,判断是否新增成功
if (result > 0){
System.out.println("success");
}else {
System.out.println("fail");
}
// 5. 释放资源
preparedStatement.close();
connection.close();
}
删除Delete:
@Test
public void testDelete() throws SQLException {
// 1. 注册驱动
// 2. 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
// 3. 预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement("delete from t_emp where emp_id=?");
// 4. 占位符赋值
preparedStatement.setDouble(1,6);
int result = preparedStatement.executeUpdate();
// 根据受影响行数,判断是否新增成功
if (result > 0){
System.out.println("success");
}else {
System.out.println("fail");
}
// 5. 释放资源
preparedStatement.close();
connection.close();
}
1.6 实体类和ORM思想
- 在使用JDBC操作数据库时,我们会发现数据都是零散的,明明在数据库中是一行完整的数据,到了Java中变成了一个一个的变量,不利于维护和管理。而我们Java是面向对象的,一个表对应的是一个类,一行数据就对应的是Java中的一个对象,一个列对应是对象的属性,所以我们要把数据存储在一个载体里,这个载体就是实体类!
- ORM(Object Relational Mapping思想),对象到关系数据库的映射,作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来,以面向对象的角度操作数据库中的数据,即一张表对应一个类,一行数据对应一个对象,一个列对应一个属性!
- 当下JDBC中这种过程我们称其为手动ORM。后续我们也会学习ORM框架,比如MyBatis、JPA等。
(1)设计实体类Employee
package com.gtc.advanced.pojo;
public class Employee {
private Integer id; // 对应数据库中的emp_id
private String name;// 对应数据库中的emp_name
private Double salary;// 对应数据库中的emp_salary
private Integer age;// 对应数据库中的emp_age
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", salary='" + salary + '\'' +
", age=" + age +
'}';
}
}
(2)ORM思想封装单个/多个对象
public class JDBCTest {
/**
* ORM思想封装单个对象
* @throws SQLException
*/
@Test
public void testORM() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
PreparedStatement preparedStatement = connection.prepareStatement("select emp_id, emp_name, emp_age, emp_salary from t_emp where emp_id=?");
preparedStatement.setInt(1, 1);
ResultSet resultSet = preparedStatement.executeQuery();
Employee employee = null;
if (resultSet.next()) {
employee = new Employee();
// 传统方法
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
int empAge = resultSet.getInt("emp_age");
double empSalary = resultSet.getDouble("emp_salary");
// 为对象的属性赋值(ORM思想体现)
employee.setId(empId);
employee.setName(empName);
employee.setAge(empAge);
employee.setSalary(empSalary);
}
System.out.println(employee);
resultSet.close();
preparedStatement.close();
connection.close();
}
/**
* ORM思想封装多个对象
* @throws SQLException
*/
@Test
public void testORMList() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
PreparedStatement preparedStatement = connection.prepareStatement("select emp_id, emp_name, emp_age, emp_salary from t_emp");
ResultSet resultSet = preparedStatement.executeQuery();
Employee employee = null;
List<Employee> list = new ArrayList<Employee>();
while (resultSet.next()) {
employee = new Employee();
// 传统方法
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
int empAge = resultSet.getInt("emp_age");
double empSalary = resultSet.getDouble("emp_salary");
// 为对象的属性赋值(ORM思想体现)
employee.setId(empId);
employee.setName(empName);
employee.setAge(empAge);
employee.setSalary(empSalary);
list.add(employee);
}
System.out.println(list);
resultSet.close();
preparedStatement.close();
connection.close();
}
}
1.7 连接池
现有问题:
- 每次操作数据库都要获取新连接,使用完毕后就close释放,频繁的创建和销毁造成资源浪费。
- 连接的数量无法把控,对服务器来说压力巨大
解决办法——连接池:
- 连接池就是数据库连接对象的缓冲区,通过配置,有连接池负责创建连接、管理连接、释放连接等操作。预先创建数据库连接放入连接池,用户在请求时,通过池直接获取连接,使用完毕后,将连接池放回池中,避免了频繁的创建和销毁,同时解决了创建的效率。
- 当池中无连接可用,且未达到上限时,连接池会新建连接。
- 池中连接达到上限,用户请求等待,可以设置超时时间。
常见连接池:
JDBC的数据库连接池使用javax.sql.DataSource接口进行规范,所有的第三方连接池都实现此接口,自行添加具体实现!也就是说,所有连接池获取连接的和回收连接方法都一样,不同的只有性能和扩展功能!
- Druid是阿里提供的数据库连接池,是集DBCP、C3P0、Proxool优点于一身的数据库连接池,性能、扩展性、易用性都更好,功能丰富。
- Hikari取自日语,是光的意思,是SpringBoot2.x之后内置的一款连接池。
追求效率用Hikari,追求可扩展性用Druid
1.7.1 Druid连接池
硬编码实现:
public class DruidTest {
public static void main(String[] args) throws SQLException {
/**
* 硬编码: 将连接池的配置信息和Java代码耦合在一起
* 1. 创建DruidDataSource连接池对象
* 2. 设置连接池的配置信息
* 3. 通过连接池获取连接对象
* 4. 回收连接【不是释放连接,而是将连接归还给连接池,给其他线程进行复用】
*/
//1. 创建DruidDataSource连接池对象
DruidDataSource ds = new DruidDataSource();
//2.1 设置连接池的配置信息
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://127.0.0.1:3306/atguigu");
ds.setUsername("root");
ds.setPassword("root");
// 2.2 非必须设置的配置
ds.setInitialSize(10);
ds.setMaxActive(20);
//3. 通过连接池获取连接对象
Connection connection = ds.getConnection();
//4. 基于Connection进行CRUD
//4. 编写SQL语句
Statement statement = connection.createStatement();
String sql = "select emp_id, emp_name, emp_salary, emp_age from t_emp where emp_name='康师傅'";
//5. 执行SQL语句(返回结果集)
ResultSet resultSet = statement.executeQuery(sql);
//6. 处理结果,遍历resultSet结果集
while (resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
String empSalary = resultSet.getString("emp_salary");
String emp_age = resultSet.getString("emp_age");
System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+emp_age);
}
//5. 回收连接(不是关闭)
connection.close();
}
}
软编码实现:
在项目目录下创建resources文件夹,标识该文件夹为资源目录,创建db.properties配置文件,将连信息定义在该文件中。
propeties类型文件通常用于存储Key-Value键值对,且Key和Value均为字符串类型。
public class DruidSoftTest {
public static void main(String[] args) throws Exception {
// 1.创建一个Properties集合,用于存储外部配置文件的key和value值
Properties prop = new Properties();
// 2. 读取外部配置文件,获取输入流,加载到Properties集合里
InputStream inputStream = DruidSoftTest.class.getClassLoader().getResourceAsStream("druid.properties");
prop.load(inputStream);
// 3. 基于Properties集合构建DruidDataSource连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
// 4. 通过连接池获取连接对象
Connection connection = dataSource.getConnection();
// 5. CRUD
Statement statement = connection.createStatement();
String sql = "select emp_id, emp_name, emp_salary, emp_age from t_emp where emp_name='康师傅'";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
String empSalary = resultSet.getString("emp_salary");
String emp_age = resultSet.getString("emp_age");
System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+emp_age);
}
// 6. 回收连接
connection.close();
}
}
1.7.2 Hikari连接池
略
1.8 工具类封装
/**
* JDBC工具类 (V1.0)
* 1. 维护一个连接池对象
* 2. 对外提供在连接池中获取连接的方法
* 3. 对外提供回收连接的方法
* 注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法!
*/
public class JdbcUtil {
// 创建连接池引用,因为要提供给当前项目的全局使用,所以创建是静态的
private static DataSource dataSource;
// 项目启动时,即创建连接池对象,赋值给dataSource
static {
try{
Properties properties = new Properties();
InputStream inputStream = JdbcUtil.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
}catch (Exception e){
throw new RuntimeException(e);
}
}
// 对外提供在连接池中获取连接的方法
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void release(Connection conn){
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
测试工具类是否能使用
public class JdbcUtilTest {
@Test
public void test() {
Connection connection = JdbcUtil.getConnection();
System.out.println("crud...");
JdbcUtil.release(connection);
}
}
1.9 DAO概念及搭建
- DAO:Data Acess Object,数据访问对象
- Java是面向对象语言,数据在Java中通常以对象的形式存在。一张表对应一个实体类,一张表的操作对应一个DAO对象。
- 在Java操作数据库时,我们会将对同一张表的增删改查操作统一维护起来,维护的这个类就是DAO层。
- DAO层只关注对数据库的操作,供业务层Service调用。
BaseDAO概念
基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以我们可以抽取公共代码,给这些DAO的实现类抽取一个公共的父类,复用增删改查的基本操作,我们称为BaseDao
1.10 数据库事务操作
1.11 事务实现