数据访问和DAO模式
一、学习目标
- 掌握DAO模式
- 掌握Properties配置文件的使用方法
二、数据持久化
2.1数据持久化概念
- 将程序中的数据在瞬时状态和持久状态间转换的机制即为 数据持久化
2.2持久化的实现方式
- 数据库
- 普通文件
- XML文件
三、为什么进行JDBC封装
下面代码有什么缺点?
package com.aiden.jdbc; import java.sql.*; import java.util.Scanner; /** * @author Aiden */ public class UserLogin { public static void main(String[] args) { //业务操作提示 Scanner input=new Scanner(System.in); System.out.println("请输入用户名:"); String userName=input.next();//用户名 System.out.println("请输入密码:"); String userPwd=input.next();//密码 //访问数据库 boolean isLogin = login(userName, userPwd); //业务操作判断 if(isLogin){ System.out.println("登录成功"); }else{ System.out.println("登录失败!用户名密码错误"); } } //登录验证方法 public static boolean login(String userName,String userPwd){ boolean isLogin=false; //访问数据库 Connection conn =null; PreparedStatement stmt=null; ResultSet rs=null; try { Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/t147hospital?serverTimeZone=UTC","root","root"); //1.sql语句 String sql="select * from user where userName=? and userPwd=?"; //2.实例化执行对象prepareStatement stmt=conn.prepareStatement(sql); //3.设置参数 stmt.setObject(1,userName); stmt.setObject(2,userPwd); //4.调用执行方法 rs=stmt.executeQuery(); if (rs!=null&&rs.next()){ isLogin=true; }else{ isLogin=false; } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); }finally { //关闭资源 try { if(rs!=null){ rs.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if(stmt!=null){ stmt.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if(conn!=null){ conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } return isLogin; } }
存在问题:
解决方案:
采用面向接口编程,可以降低代码间的耦合性
面向接口编程优势:
- 隔离业务逻辑代码和数据访问代码
- 隔离不同数据库的实现
四、JDBC的初步封装实现
分析:
- 通用的数据访问操作:
- 数据库连接的建立和关闭操作
- 增、删、改 查方法
- 是否能进一步优化代码 ???
解决方案:
- 将通用的操作(打开、关闭连接等)封装到工具类
BaseDao
BaseDao封装类
包的命名:小写 com.hospital.dao
package com.hospital.dao; import java.sql.*; /** * 数据库访问工具类 * * @author Aiden */ public class BaseDao { private String driver = "com.mysql.cj.jdbc.Driver"; private String url = "jdbc:mysql://localhost:3306/t147hospital?serverTimezone=Asia/Shanghai"; private String user = "root"; private String password = "root"; protected Connection conn;//连接对象 /** * 建立连接 * * @return */ public Connection getConnection() { try { //加载驱动 Class.forName(driver); //建立连接 conn = DriverManager.getConnection(url, user, password); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return conn; } /** * 关闭资源 * * @param conn 连接对象 * @param stmt 执行对象 * @param rs 结果集 */ public void closeAll(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 增 删 改 方法 * 无参:int result=executeUpdate(sql); * 有参:int result=executeUpdate(sql,id); * 有参:int result=executeUpdate(sql,name,age,sex); * * @param sql 要执行的sql命令 * @param params 参数(可选参数) * @return 受影响的行数 int类型 * @throws SQLException */ public int executeUpdate(String sql, Object... params) throws SQLException { int result = 0; //声明执行对象 PreparedStatement stmt = null; try { //建立连接 this.conn = getConnection(); //实例化执行对象 stmt = conn.prepareStatement(sql); //如果有参数则进行参数设置 if (params != null && params.length > 0) { //循环设置参数 for (int i = 0; i < params.length; i++) { stmt.setObject((i + 1), params[i]); } } //开始执行 result = stmt.executeUpdate(); } catch (SQLException e) { throw new RuntimeException("访问数据库异常!", e); } finally { //关闭资源 this.closeAll(conn, stmt, null); } return result; } /** * 查询方法 * (需要手动释放资源) * 无参:ResultSet rs=executeQuery(sql); * 有参:ResultSet rs=executeQuery(sql,name); * 有参:ResultSet rs=executeQuery(sql,name,sex); * * @param sql 要执行的sql命令 * @param params 参数(可选参数) * @return ResultSet 结果集对象 * @throws SQLException */ public ResultSet executeQuery(String sql, Object... params) throws SQLException { //结果集 ResultSet rs = null; //声明执行对象 PreparedStatement stmt = null; try { //建立连接 this.conn = getConnection(); //实例化执行对象 stmt = conn.prepareStatement(sql); //如果有参数则进行参数设置 if (params != null && params.length > 0) { //循环设置参数 for (int i = 0; i < params.length; i++) { stmt.setObject((i + 1), params[i]); } } //执行查询请求 rs = stmt.executeQuery(); } catch (SQLException e) { throw new RuntimeException("访问数据库异常!", e); } return rs; } }
测试调用
- 数据表结构
drop database if exists `t147hospital`; #创库 create database if not exists t147hospital; use t147hospital; drop table if exists `user`; #创表 create table `user`( `userId` int(0) not null auto_increment comment '主键编号', `userName` varchar(50) not null comment '用户名', `userPwd` varchar(50) not null comment '密码', primary key (`userId`) using btree ) ; #插入模拟数据 insert into `user` values (1, 'admin', '123456'); insert into `user` values (2, 't147', '123456');
- 测试代码
package com.hospital.test; import com.hospital.dao.BaseDao; import java.sql.SQLException; /** * @author Aiden */ public class TestJDBCBaseDao extends BaseDao { //测试BaseDao增删改方法 public static void main(String[] args) { TestJDBCBaseDao dao=new TestJDBCBaseDao(); if (dao.delete()) { System.out.println("操作成功!"); }else{ System.out.println("操作失败!!!"); } } //测试新增 public boolean insert() { int result = 0; String sql = "insert into `user` (userName,userPwd) values(?,?);"; try { result = executeUpdate(sql, "aiden", "123456"); } catch (SQLException e) { e.printStackTrace(); } return result > 0; } //测试修改 public boolean update() { int result = 0; String sql = "update user set userPwd=? where userId=?;"; try { result = executeUpdate(sql, "666666",3); } catch (SQLException e) { e.printStackTrace(); } return result > 0; } //测试删除 public boolean delete() { int result = 0; String sql = "delete from user where userId=?;"; try { result = executeUpdate(sql, 3); } catch (SQLException e) { e.printStackTrace(); } return result > 0; } }
五、什么是DAO?
非常流行的数据访问模式——DAO模式
Data Access Object(数据存取对象)
位于业务逻辑和持久化数据之间
实现对持久化数据的访问
五、DAO模式的组成与介绍
5.1组成部分
数据库工具类 BaseDao =>位于
dao
包下实体类 => 位于
entity
包下DAO接口 =>位于
dao
包下DAO实现类 =>位于
dao.impl
包下
5.2实体类(entity)
- 实体类(Entity)是Java应用程序中与数据库表对应的类
- 用于存储数据,并提供对这些数据的访问
- 通常,实现类是持久的,需要存储于文件或数据库中
- 访问操作数据库时,以实体类的方式组织数据库中的实体及关系
- 通常,在Java工程中创建一个名为entity的Package,用于集中保存实体类
- 一个数据库表对应一个实体类
- 实体类特征
- 属性一般使用
private
修饰- 提供public修饰的
getter/setter
方法- 实体类提供无参构造方法,根据业务提供有参构造
- 实现
java.io.Serializable
接口,支持序列化机制,可以将该对象转换成字节序列而保存在磁盘上或在网络上传输- 如果实体类实现了
java.io.Serializable
接口,应该定义属性serialVersionUID
,解决不同版本之间的序列化问题- 为
serialVersionUID
赋值的方法- 一旦为一个实体类的serialVersionUID赋值,就不要再修改;否则,在反序列化之前版本的数据时,会报java.io.InvalidClassException异常
5.3定义实体类
package com.hospital.entity; import java.io.Serializable; import java.util.Date; /** * 病人信息实体类 */ public class Patient implements Serializable { //定义属性serialVersionUID,解决不同版本之间的序列化问题 private static final long serialVersionUID = -6418684824125291382L; //属性一般使用`private`修饰 private Integer patientId;//病人编号 private String password;//密码 private Date birthDate;//出生日期 private String gender;//性别 private String patientName;//姓名 private String phoneNum;//手机 private String email;//邮箱 private String identityNum;//身份证 private String address;//联系地址 //无参构造 public Patient() { } //有参构造 public Patient(Integer patientId, String password, Date birthDate, String gender, String patientName, String phoneNum, String email, String identityNum, String address) { this.patientId = patientId; this.password = password; this.birthDate = birthDate; this.gender = gender; this.patientName = patientName; this.phoneNum = phoneNum; this.email = email; this.identityNum = identityNum; this.address = address; } //getter/setter方法 public Integer getPatientId() { return patientId; } public void setPatientId(Integer patientId) { this.patientId = patientId; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getPatientName() { return patientName; } public void setPatientName(String patientName) { this.patientName = patientName; } public String getPhoneNum() { return phoneNum; } public void setPhoneNum(String phoneNum) { this.phoneNum = phoneNum; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getIdentityNum() { return identityNum; } public void setIdentityNum(String identityNum) { this.identityNum = identityNum; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
5.4使用实体类传递数据
@Override public List<Patient> findAll() throws SQLException { String sql = "select patientId,patientName,gender,birthDate,email from patient"; List<Patient> list = new ArrayList<>(); //执行查询请求,并将查询保存至结果集ResultSet中 ResultSet rs = this.executeQuery(sql); //循环取出结果集中的数据,并添加至泛型集合List中,以此实现使用实体类传递数据 while (rs.next()) { Patient patient = new Patient(); patient.setPatientId(rs.getInt("patientId")); patient.setPatientName(rs.getString("patientName")); patient.setGender(rs.getString("gender")); patient.setBirthDate(rs.getDate("birthDate")); patient.setEmail(rs.getString("email")); list.add(patient);//循环添加数据 } //关闭资源 this.closeAll(conn, null, rs); //返回最终从结果集获得的集合数据 return list; }
5.5Dao接口
package com.hospital.dao; import com.hospital.entity.Patient; import java.sql.SQLException; import java.util.List; /** * 病人DAO接口 * * @author Aiden */ public interface PatientDao { /** * 查询多条数据 * * @return */ List<Patient> findAll() throws SQLException; /** * 根据病人名字模糊查询 * * @param patientName 病人姓名 * @return */ List<Patient> findByPatientName(String patientName) throws SQLException; /** * 查询单个对象 * * @param patientId * @return */ Patient findById(Integer patientId) throws SQLException; /** * 新增 * * @param patient 病人对象 * @return */ int insert(Patient patient) throws SQLException; /** * 修改 * * @param patient 病人对象 * @return */ int update(Patient patient) throws SQLException; /** * 删除 * * @param patientId 病人编号 * @return */ int delete(Integer patientId) throws SQLException; }
5.6Dao实现
package com.hospital.dao.impl; import com.hospital.dao.BaseDao; import com.hospital.dao.PatientDao; import com.hospital.entity.Patient; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * Dao接口实现类 * * @author Aiden */ public class PatientDaoImpl extends BaseDao implements PatientDao { @Override public List<Patient> findAll() throws SQLException { String sql = "select patientId,patientName,gender,birthDate,email from patient"; List<Patient> list = new ArrayList<>(); //执行查询请求,并将查询保存至结果集ResultSet中 ResultSet rs = this.executeQuery(sql); //循环取出结果集中的数据,并添加至泛型集合List中,以此实现使用实体类传递数据 while (rs.next()) { Patient patient = new Patient(); patient.setPatientId(rs.getInt("patientId")); patient.setPatientName(rs.getString("patientName")); patient.setGender(rs.getString("gender")); patient.setBirthDate(rs.getDate("birthDate")); patient.setEmail(rs.getString("email")); list.add(patient);//循环添加数据 } //关闭资源 this.closeAll(conn, null, rs); return list; } @Override public List<Patient> findByPatientName(String patientName) throws SQLException { String sql = "select patientId,patientName,gender,birthDate,email from patient where patientName like CONCAT('%',?,'%')"; List<Patient> list = new ArrayList<>(); //执行查询请求,并将查询保存至结果集ResultSet中 ResultSet rs = this.executeQuery(sql, patientName); //循环取出结果集中的数据,并添加至泛型集合List中,以此实现使用实体类传递数据 while (rs.next()) { Patient patient = new Patient(); patient.setPatientId(rs.getInt("patientId")); patient.setPatientName(rs.getString("patientName")); patient.setGender(rs.getString("gender")); patient.setBirthDate(rs.getDate("birthDate")); patient.setEmail(rs.getString("email")); list.add(patient);//循环添加数据 } //关闭资源 this.closeAll(conn, null, rs); return list; } @Override public Patient findById(Integer patientId) throws SQLException { String sql = "select patientId,patientName,gender,birthDate,email from patient where patientId =?"; Patient patient = null; ResultSet rs = this.executeQuery(sql, patientId); //判断是否存在数据,如果有则取出存放到Patient实体类中,以此实现使用实体类传递数据 if (rs.next()) { patient = new Patient(); patient.setPatientId(rs.getInt("patientId")); patient.setPatientName(rs.getString("patientName")); patient.setGender(rs.getString("gender")); patient.setBirthDate(rs.getDate("birthDate")); patient.setEmail(rs.getString("email")); } //关闭资源 this.closeAll(conn, null, rs); return patient; } @Override public int insert(Patient patient) throws SQLException { String sql = "INSERT INTO `patient`(`password`, `birthDate`, `gender`, `patientName`, `phoneNum`, `email`, `identityNum`, `address`) VALUES (?,?,?,?,?,?,?,?)"; return this.executeUpdate(sql, patient.getPassword(), patient.getBirthDate(), patient.getGender(), patient.getPatientName(), patient.getPhoneNum(), patient.getEmail(), patient.getIdentityNum(), patient.getAddress()); } @Override public int update(Patient patient) throws SQLException { return 0; } @Override public int delete(Integer patientId) throws SQLException { String sql = "delete from patient where patientId=?"; return this.executeUpdate(sql, patientId); } }
六、Properties类
6.0为什么使用Properties类
使用JDBC技术访问数据库数据的关键代码
- 如下代码不难发现,当我们数据库密码修改后,此时我们java源代码也要重新修改编译!
- 在真实项目开发部署中,我们往往将程序中经常变动的信息以配置文件存储,让用户脱离程序本身修改相关的变量设置,而不需要重新修改java源代码,省去编译部署环节,也让程序的可扩展性得到很好的利用。
package com.hospital.dao; import java.sql.*; /** * 数据库访问工具类 */ public class BaseDao { private String driver = "com.mysql.cj.jdbc.Driver"; private String url = "jdbc:mysql://localhost:3306/t147hospital?serverTimezone=Asia/Shanghai"; private String user = "root"; private String password = "root"; protected Connection conn;//连接对象 public Connection getConnection() { try { Class.forName(driver); //加载驱动 conn = DriverManager.getConnection(url, user, password);//建立连接 } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return conn; } }
6.1使用配置文件优势
- Java中的配置文件常为
xxx.properties
文件- 后缀为
.properties
- 格式是
“键=值”
格式- 使用
“#”
来注释- 通常,为数据库访问添加的配置文件是
database.properties
6.2创建database.properties文件
#MySql8.0驱动 jdbc.driver=com.mysql.cj.jdbc.Driver #MySql8.0数据库连接地址信息(含主机localhost、端口3306、数据库t147hospital、启用Unicode字符集useUnicode=true、字符编码为characterEncoding=utf8、与服务器进行通信时不使用SSL、时区=Asia/Shanghai 等设置) jdbc.url=jdbc:mysql://localhost:3306/t147hospital?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai #MySql8.0数据库用户名 jdbc.username=root #MySql8.0数据库密码 jdbc.password=root
6.2Properties类读取配置
Java中提供了Properties类来读取配置文件
方法名 说 明 String getProperty(String key)
用指定的键在此属性列表中搜索属性。通过参数key得到其所对应的值 Object setProperty(String key,String value)
调用Hashtable的方法put。通过调用基类的put()方法来设置键-值对 void load(InputStream inStream)
从输入流中读取属性列表 (键和元素对)。通过对指定文件进行装载获取该文件中所有键-值对 void clear() 清除所装载的键-值对,该方法由基类Hashtable提供 创建ConfigManager类
package com.hospital.utils; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * 配置文件操作工具类 * * @author Aiden */ public class ConfigManager { private static Properties properties = null; static { InputStream inputStream = null; inputStream = ConfigManager.class.getClassLoader().getResourceAsStream("database.properties"); if (inputStream == null) { throw new RuntimeException("找不到database.properties数据库参数配置文件!"); } //实例化Properties对象 properties = new Properties(); //从输入流中读取属性列表 try { properties.load(inputStream); } catch (IOException e) { throw new RuntimeException("数据库配置参数加载错误!", e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 根据key获取properties文件中的value值 * @param key * @return */ public static String getProperty(String key) { return properties.getProperty(key); } }
改造BaseDao
public class BaseDao { //使用ConfigManager工具类读取配置文件的信息(注意key要保持一致,否则会出现读取不到值) private String driver = ConfigManager.getProperty("jdbc.driver"); private String url = ConfigManager.getProperty("jdbc.url"); private String user = ConfigManager.getProperty("jdbc.username"); private String password = ConfigManager.getProperty("jdbc.password"); protected Connection conn;//连接对象 public Connection getConnection() { try { //加载驱动 Class.forName(driver); //建立连接 conn = DriverManager.getConnection(url, user, password); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return conn; } }
6.3读取配置时注意事项