java程序员入行科目一之CRUD轻松入门教程(二)
封装工具类
封装获取连接&释放资源操作
在实际使用JDBC的时候,很多操作都是固定的,没有必要每次都去注册驱动,获取链接对象等等。
同样,释放资源的close操作也可以封装一下
下面是封装好的具体工具类
package com.jimihua.utils;
import java.sql.*;
/**
* 数据库操作的一个通用的内容
*/
public class DatabaseUtils {
// 注册驱动的操作
static{
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 获取Connection对象方法
* @return
*/
public static Connection getConnection() {
try {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?characterEncoding=utf-8", "root", "root");
return connection;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("获取Connection出错!");
}
}
/**
* 针对查询操作的释放资源
* @param connection
* @param statement
* @param resultSet
*/
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
if(resultSet == null || statement == null || connection == null){
throw new NullPointerException("参数传递的connection,statement,resultSet不允许为NULL");
}
try {
resultSet.close();
statement.close();
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 针对写操作的释放资源
* @param connection
* @param statement
*/
public static void closeAll(Connection connection, Statement statement) {
if( statement == null || connection == null){
throw new NullPointerException("参数传递的connection,statement,resultSet不允许为NULL");
}
try {
statement.close();
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
再次迭代之前的Demo4为Demo5。
package com.jimihua;
import com.jimihua.utils.DatabaseUtils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Scanner;
/**
* 实现注册和登录操作
* 基于SQL注入的问题,在这里采用PreparedStatement来解决SQL注入问题。
*/
public class Demo5 {
public static void main(String[] args) throws Exception {
Scanner input = new Scanner(System.in);
while(true){
// 基于提示,做具体什么操作
System.out.println("当前可以选择注册或者登录");
System.out.println("注册操作输入 1");
System.out.println("登录操作输入 2");
int i = input.nextInt();
// 因为无论是注册还是登录,都需要用户输入用户名和密码
System.out.println("请输入用户名:");
String username = input.next();
System.out.println("请输入密码:");
String password = input.next();
// 根据i执行不同的逻辑
if(i == 1){
// 注册操作
//1、获取链接
Connection conn = DatabaseUtils.getConnection();
//2、准备注册的insert的SQL
String sql = "insert into user (username,password) values (?,?)";
//3.1、拿到statement
PreparedStatement ps = conn.prepareStatement(sql);
//3.2 给占位符?赋值
ps.setString(1,username);
ps.setString(2,password);
//4、执行SQL
int count = ps.executeUpdate();
//5、根据返回结果基于提示
if(count == 1){
System.out.println("注册成功!!");
}
//6、释放资源
DatabaseUtils.closeAll(conn,ps);
}else {
// 登录操作
//1、获取链接
Connection conn = DatabaseUtils.getConnection();
//2、准备登录的查询SQL
String sql = "select * from user where username = ? and password = ?";
//3.1、拿到statement
PreparedStatement ps = conn.prepareStatement(sql);
//3.2、给?赋值
ps.setString(1,username);
ps.setString(2,password);
//4、执行SQL
ResultSet resultSet = ps.executeQuery();
if (resultSet.next()){
// 到这,说明用户名和密码正确,登录成功!
System.out.println("用户名和密码正确,登录成功!");
}else{
// 到这,说明用户名和密码错误,登录失败!
System.out.println("用户名和密码错误,登录失败!");
}
//5、释放资源
DatabaseUtils.closeAll(conn,ps,resultSet);
}
}
}
}
跨平台方案
现在将连接数据库的url,username,password都是写死在Java代码中的。
正常运行的项目需要编译成.class文件运行,编译过后再想修改url,username,password就挺难的。
咱们利用Hashtable的子类,Properties,将连接数据库的信息卸载外部的.properties文件中,在Java代码里,通过Properties类,将外部的.properties文件的内容加载到内存里使用。
分成几步准备
- 第一步,外部声明好一个database.properties文件,编写好连接数据库的信息
- 第二步,在Java的DatabaseUtils的工具类中,加载database.properties文件,读取连接信息
package com.jimihua.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* 数据库操作的一个通用的内容
*/
public class DatabaseUtils {
private static final Properties PROP = new Properties();
public static void main(String[] args) {
DatabaseUtils db = new DatabaseUtils();
}
// 注册驱动的操作
static{
try {
// 优先通过PROP对象,加载database.properties文件
InputStream is = DatabaseUtils.class.getResourceAsStream("/database.properties");
PROP.load(is);
Class.forName(PROP.getProperty("jdbc.driver"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 获取Connection对象方法
* @return
*/
public static Connection getConnection() {
try {
Connection connection = DriverManager.getConnection(PROP.getProperty("jdbc.url"),PROP.getProperty("jdbc.username"),PROP.getProperty("jdbc.password"));
return connection;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("获取Connection出错!");
}
}
// 省略释放资源代码!!!!
}
ORM
所谓的ORM,其实就是Object Relational Mapping,也就是对象关系映射。
O就代表你Java中的实体类,R就是你数据库中的具体表。
M就是让O和R建立一个联系。
本质的意愿,咱们在操作数据库时,需要用到SQL语句。ORM的愿景是希望使用面向对象的思维来和数据库交互,不需要了解SQL到底是什么。这种咱们成为 完整的ORM框架 能够提供的一个效果。
User findById(1); --- select * from user where id = 1; int save(User); --- insert into user (id,username,password) values (?,?,?);
但是,这种完整的ORM框架会让咱们程序员对SQL的把控很低,现在国内主流的是一款 半自动化的ORM框架 ,这种ORM框架依然需要咱们写SQL语句。
而咱们不能直接干一个框架出来,现在的目的:
- 准备好对应jdbc-test库中的user表的一个实体类,User类。
- 在查询User表时,将查询到的结果封装到User类中操作。
准备好实体类User:
package com.jimihua.entity;
/**
* 当前是一个实体类,目的是和数据库中的关系表产生映射关系
*/
public class User {
// 全部的基本数据类型,采用包装类,因为包装类可以多存储一个NULL,从而尽可能的避免出现NullPointException
/** 主键Id */
private Long id;
/** 用户名 */
private String username;
/** 密码 */
private String password;
public User() {}
public User(Long id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
将查询数据库表User的结果,映射到User实体类中
package com.jimihua;
import com.jimihua.entity.User;
import com.jimihua.utils.DatabaseUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 是为了完成查询user表后,将user表的ResultSet结果封装到User对象中
*/
public class Demo6 {
public static void main(String[] args) throws SQLException {
//1、获取连接对象
Connection conn = DatabaseUtils.getConnection();
//2、准备查询user信息的SQL
String sql = "select * from user where id = ?";
//3、获取PreparedStatement,传入SQL
PreparedStatement ps = conn.prepareStatement(sql);
//4、给占位符赋值
ps.setLong(1,1);
//5、执行SQL,获取ResultSet返回结果
ResultSet rs = ps.executeQuery();
//6、遍历ResultSet结果,封装到User
while(rs.next()){
User user = new User();
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
System.out.println("查询到的user表信息:" + user);
}
//7、释放资源
DatabaseUtils.closeAll(conn, ps, rs);
}
}
DAO数据访问对象层
DAO层专门实现和数据库交互的操作,不参与任何逻辑。
DAO层单独封装出来的目的是为了达到解耦的效果,也就是让咱们的程序达到一个低耦合的效果。
- 对同一张表的增删改查操作都封装到一个XxxDao类中。
- 针对这个XxxDao类里提供对应的增删改查操作(insert、update、delete、findById、findAll)
现在就需要将XxxDaoImpl实现出来,如下图:
准备一个案例操作。
准备关系表
创建一张person表,提供下述字段:
- id:int类型,主键自增
- name:varchar类型,非空
- age:int类型,非空
- born_date:date类型,非空
- email:varchar类型
- address:varchar类型
create table person( id int primary key auto_increment comment '主键', name varchar(16) not null comment '名字', age int not null comment '年龄', born_date date not null comment '出生日期', email varchar(64) comment 'email', address varchar(256) comment '地址' );
自己再准备几条测试数据
准备实体类
准备Person类映射前面构建好的person表
package com.jimihua.entity;
import java.util.Date;
/**
* 映射jdbc_test库中的person表
*/
public class Person {
private Integer id;
private String name;
private Integer age;
private Date bornDate;
private String email;
private String address;
// 省略 无参,有参,toString,get/set
}
准备DAO接口与实现类
先准备到PersonDao接口,提供抽象方法,对外提供哪些功能
package com.jimihua.dao;
import com.jimihua.entity.Person;
import java.util.List;
/**
* 构建与Person表交互的DAO层接口
*/
public interface PersonDao {
/**
* 插入一条Person数据
* @param person
* @return
*/
int insert(Person person);
/**
* 根据Id修改person信息
* @param person
* @return
*/
int updateById(Person person);
/**
* 根据id删除一条person
* @param id
* @return
*/
int deleteById(Integer id);
/**
* 根据id查询一条person信息
* @param id
* @return
*/
Person findById(Integer id);
/**
* 查询全部person信息
* @return
*/
List<Person> findAll();
}
提供Dao层对应的实现类。
package com.jimihua.dao.impl;
import com.jimihua.dao.PersonDao;
import com.jimihua.entity.Person;
import com.jimihua.utils.DatabaseUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* 实现PersonDao接口,实现内5个方法。
*/
public class PersonDaoImpl implements PersonDao {
@Override
public int insert(Person person) throws SQLException {
//1、获取连接
Connection conn = DatabaseUtils.getConnection();
//2、编写SQL语句
String sql = "insert into person (name,age,born_date,email,address) values (?,?,?,?,?)";
//3、获取PreparedStatement,传入SQL
PreparedStatement ps = conn.prepareStatement(sql);
//4、给占位符赋值
ps.setString(1, person.getName());
ps.setInt(2, person.getAge());
ps.setDate(3,new java.sql.Date(person.getBornDate().getTime()));
ps.setString(4,person.getEmail());
ps.setString(5, person.getAddress());
//5、执行SQL,获取结果
int count = ps.executeUpdate();
//6、释放资源
DatabaseUtils.closeAll(conn,ps);
//7、返回结果
return count;
}
@Override
public int updateById(Person person) throws SQLException {
//1、获取连接
Connection conn = DatabaseUtils.getConnection();
//2、编写SQL语句
String sql = "update person set name=?,age=?,born_date=?,email=?,address = ?where id=?";
//3、获取PreparedStatement,传入SQL
PreparedStatement ps = conn.prepareStatement(sql);
//4、给占位符赋值
ps.setString(1, person.getName());
ps.setInt(2, person.getAge());
ps.setDate(3, new java.sql.Date(person.getBornDate().getTime()));
ps.setString(4, person.getEmail());
ps.setString(5, person.getAddress());
ps.setInt(6, person.getId());
//5、执行SQL,获取结果
int count = ps.executeUpdate();
//6、释放资源
DatabaseUtils.closeAll(conn,ps);
//7、返回结果
return count;
}
@Override
public int deleteById(Integer id) throws SQLException {
//1、获取连接
Connection conn = DatabaseUtils.getConnection();
//2、编写SQL语句
String sql = "delete from person where id = ?";
//3、获取PreparedStatement,传入SQL
PreparedStatement ps = conn.prepareStatement(sql);
//4、给占位符赋值
ps.setInt(1,id);
//5、执行SQL,获取结果
int count = ps.executeUpdate();
//6、释放资源
DatabaseUtils.closeAll(conn,ps);
//7、返回结果
return count;
}
@Override
public Person findById(Integer id) throws SQLException {
//1、获取连接
Connection conn = DatabaseUtils.getConnection();
//2、编写SQL语句
String sql = "select * from person where id = ?";
//3、获取PreparedStatement,传入SQL
PreparedStatement ps = conn.prepareStatement(sql);
//4、给占位符赋值
ps.setInt(1,id);
//5、执行SQL,获取结果集ResultSet
ResultSet resultSet = ps.executeQuery();
//6、处理结果集封装数据
Person person = null;
if(resultSet.next()){
person = new Person();
person.setId(resultSet.getInt("id"));
person.setName(resultSet.getString("name"));
person.setAge(resultSet.getInt("age"));
person.setBornDate(resultSet.getDate("born_date"));
person.setEmail(resultSet.getString("email"));
person.setAddress(resultSet.getString("address"));
}
//7、释放资源
DatabaseUtils.closeAll(conn,ps,resultSet);
//8、返回结果
return person;
}
@Override
public List<Person> findAll() throws SQLException {
//1、获取连接
Connection conn = DatabaseUtils.getConnection();
//2、编写SQL语句
String sql = "select * from person";
//3、获取PreparedStatement,传入SQL
PreparedStatement ps = conn.prepareStatement(sql);
//4、执行SQL,获取结果集ResultSet
ResultSet rs = ps.executeQuery();
//5、处理结果集封装数据
List<Person> personList = new ArrayList<Person>();
while (rs.next()){
Person person = new Person();
person.setId(rs.getInt("id"));
person.setName(rs.getString("name"));
person.setAge(rs.getInt("age"));
person.setBornDate(rs.getDate("born_date"));
person.setEmail(rs.getString("email"));
person.setAddress(rs.getString("address"));
// 记得封装完person,add到前面构建好的List集合中!
personList.add(person);
}
//6、释放资源
DatabaseUtils.closeAll(conn,ps,rs);
//7、返回结果
return personList;
}
}
在main方法中测试
package com.jimihua;
import java.sql.SQLException;
/**
* 为了测试DAO层的对象功能是否可以正常使用
*/
public class Demo7 {
public static void main(String[] args) throws SQLException {
/*
// 测试插入数据到Person表
Person person = new Person();
person.setName("赵六");
person.setAge(26);
person.setBornDate(new Date());
person.setEmail("jimihua@gmail.com");
person.setAddress("北京市海淀区北清路aaaa");
// 准备Dao层对象
PersonDao personDao = new PersonDaoImpl();
int count = personDao.insert(person);
System.out.println(count);
*/
/*
// 测试修改Person表中的数据
Person person = new Person();
person.setId(4);
person.setName("赵六六");
person.setAge(266);
person.setBornDate(new Date());
person.setEmail("jimihua@126.com");
person.setAddress("北京市海淀区苏州街tttt");
// 准备Dao层对象
PersonDao personDao = new PersonDaoImpl();
int count = personDao.updateById(person);
System.out.println(count);
*/
/*
// 测试删除Person表中的数据
Integer id = 4;
// 准备Dao层对象
PersonDao personDao = new PersonDaoImpl();
int count = personDao.deleteById(id);
System.out.println(count);
*/
/*
// 测试根据Id查询Person信息
Integer id = 3;
// 准备Dao层对象
PersonDao personDao = new PersonDaoImpl();
Person person = personDao.findById(id);
System.out.println(person);
*/
/*
// 测试查询全部数据
PersonDao personDao = new PersonDaoImpl();
List<Person> list = personDao.findAll();
for (Person person : list) {
System.out.println(person);
}
*/
}
}
时间类型处理
现在MySQL驱动是8.0.33,MySQL驱动的版本如果是8.0.23之前的版本,会和现在有一些区别!
在上面操作增删改查时,咱们发现了一个问题。
基于PreparedStatement去给占位符设置时间类型时,他需要的是java.sql.Date类型,咱们不能直接基于java.util.Date,因为
所以,在基于ResultSet获取了一个时间类型时,才会发现,返回的是java.util.Date,但是我可以用java.util.Date去接收,明显是一个多态的效果,这个是没有问题的。
咱们针对处理这些时间类型,完整的来个小例子:
在MySQL端,咱们常用的有三种时间类型:date,datetime,timestamp。
在Java端,常用的时间类型,基本就是java.util.Date,java.time.LocalDateTime,偶尔可能也会使用字符串类型来接收时间java.lang.String
接收时间类型数据
MySQL端的时间类型,采用date类型,只有年月日。对应到Java中,如何接收上述的三种类型。
- java.util.Date: 因为ResultSet返回时,可以直接基于getDate获取到java.sql.Date,再加上之前聊到的这两个类的关系,所以可以直接接收。
- java.lang.String: ResultSet支持将时间类型直接以String的方式返回,getString可以直接拿到时间的字符串,如果你想再次转换,SimpleDateFormat就可以来转换。
- java.time.LocalDate: 在使用getObject方法那MySQL中date类型时,他返回的是java.sql.Date,所以想获取LocalDate类型,需要使用java.sql.Date提供的API,也就是toLocalDate方法直接转换。
MySQL端的时间类型,采用datetime类型,年月日时分秒。对应到Java中,如何接收上述的三种类型。
- java.sql.Date: 此时就不推荐使用getDate去获取MySQL中的datetime类型了,发现即便datetime中有时分秒,但是getDate返回的java.sql.Date拿不到时分秒。直接PASS……
- java.lang.String: 这个木有问题,依然可以正常的返回字符串类型,而且年月日,时分秒都可以正常的获取到。
- java.time.LocalDateTime: 这个很舒服,可以直接基于getObject方法获取到,返回的类型直接就是默认的java.time.LocalDateTime,不需要额外转换。但是需要记住,MySQL驱动的版本需要8.0.23以上才可以。
MySQL端的时间类型,采用timestamp类型,年月日时分秒。对应到Java中,如何接收上述的三种类型。
- java.sql.Date: 此时,依然不推荐使用getDate去获取时间戳,因为依然无法拿到时分秒…………
- java.lang.String: 这个木有问题,和datetime效果一样,很舒服。
- java.time.LocalDateTime: 这个不成,在getObject方法中,返回的依然是java.sql.Timestamp时间戳类型,但是咱们可以手动的基于toLocalDateTime方法直接转换为LocalDateTime类型。
传入时间类型数据
在给占位符赋值时,有的占位符需要赋值时间类型的字段,此时,Java中可以采用setDate,setString,setObject等等方式去给占位符赋值,此时是否有一些需要注意的点。
Java端,采用setDate(java.sql.Date)的方式去设置时间,对应MySQL端的三种时间类型,需要注意什么?
- date: 只传年月日,没问题,他只显示年月日~~
- datetime: 不成,因为java.sql.Date无法传递时分秒的信息,PASS……
- timestamp: 不成,因为java.sql.Date无法传递时分秒的信息,PASS……
Java端,采用setString(java.lang.String)的方式去设置时间,对应MySQL端的三种时间类型,需要注意什么?
- date: 没问题,正常传递字符串,只要满足格式即可 yyyy-MM-dd
- datetime: 没问题,正常传递字符串,只要满足格式即可 yyyy-MM-dd HH:mm:ss
- timestamp: 没问题,正常传递字符串,只要满足格式即可 yyyy-MM-dd HH:mm:ss
Java端,采用setObject(java.lang.Object)的方式去设置时间,可以传递字符串,可以传递java.util.Date,也可以传递java.time.LocalDateTime。
- date: 没问题,基本所有时间类型和字符串都认!!
- datetime: 没问题,基本所有时间类型和字符串都认!!
- timestamp: 没问题,基本所有时间类型和字符串都认!!