封装工具类
封装获取连接&释放资源操作
在实际使用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的SQLString sql = "insert into user (username,password) values (?,?)";//3.1、拿到statementPreparedStatement ps = conn.prepareStatement(sql);//3.2 给占位符?赋值ps.setString(1,username);ps.setString(2,password);//4、执行SQLint count = ps.executeUpdate();//5、根据返回结果基于提示if(count == 1){System.out.println("注册成功!!");}//6、释放资源DatabaseUtils.closeAll(conn,ps);}else {// 登录操作//1、获取链接Connection conn = DatabaseUtils.getConnection();//2、准备登录的查询SQLString sql = "select * from user where username = ? and password = ?";//3.1、拿到statementPreparedStatement ps = conn.prepareStatement(sql);//3.2、给?赋值ps.setString(1,username);ps.setString(2,password);//4、执行SQLResultSet 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;}@Overridepublic 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信息的SQLString sql = "select * from user where id = ?";//3、获取PreparedStatement,传入SQLPreparedStatement ps = conn.prepareStatement(sql);//4、给占位符赋值ps.setLong(1,1);//5、执行SQL,获取ResultSet返回结果ResultSet rs = ps.executeQuery();//6、遍历ResultSet结果,封装到Userwhile(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 {@Overridepublic 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,传入SQLPreparedStatement 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;}@Overridepublic 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,传入SQLPreparedStatement 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;}@Overridepublic int deleteById(Integer id) throws SQLException {//1、获取连接Connection conn = DatabaseUtils.getConnection();//2、编写SQL语句String sql = "delete from person where id = ?";//3、获取PreparedStatement,传入SQLPreparedStatement ps = conn.prepareStatement(sql);//4、给占位符赋值ps.setInt(1,id);//5、执行SQL,获取结果int count = ps.executeUpdate();//6、释放资源DatabaseUtils.closeAll(conn,ps);//7、返回结果return count;}@Overridepublic Person findById(Integer id) throws SQLException {//1、获取连接Connection conn = DatabaseUtils.getConnection();//2、编写SQL语句String sql = "select * from person where id = ?";//3、获取PreparedStatement,传入SQLPreparedStatement ps = conn.prepareStatement(sql);//4、给占位符赋值ps.setInt(1,id);//5、执行SQL,获取结果集ResultSetResultSet 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;}@Overridepublic List<Person> findAll() throws SQLException {//1、获取连接Connection conn = DatabaseUtils.getConnection();//2、编写SQL语句String sql = "select * from person";//3、获取PreparedStatement,传入SQLPreparedStatement ps = conn.prepareStatement(sql);//4、执行SQL,获取结果集ResultSetResultSet 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: 没问题,基本所有时间类型和字符串都认!!