学习本节需要用到的demo数据表:
CREATE TABLE `news_detail` (
`news_Id` int NOT NULL AUTO_INCREMENT,
`news_Name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '新闻的标题',
`content` varchar(255) DEFAULT NULL COMMENT '内容',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`news_Id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
1. 什么是DAO
DAO是:Data Access Object,翻译为:数据访问对象。
一种JavaEE的设计模式,专门用来做数据增删改查的类。
在实际的开发中,通常我们会将数据库的操作封装为一个单独的DAO去完成,这样做的目的是:提高代码的复用性,另外也可以降低程序的耦合度,提高扩展力。
例如:操作用户数据的叫做UserDao,操作员工数据的叫做EmployeeDao,操作产品数据的叫做ProductDao,操作订单数据的叫做OrderDao等。
DAO模式的组成部分:
- DAO接口
- DAO实现类
- 实体类
- 数据库连接和关闭工具类(baseDao)
2. 配置数据库访问参数
我不喜欢把访问jdbc.properties参数的步骤写到BaseDao中 我倾向于单开一个类对外提供静态方法来读取参数
这里我们编写类ConfigManager 读取属性文件:
package com.lfq.util;
import java.util.ResourceBundle;
/**
* @Author:lfq
* @Package:com.lfq.util
* @Filename:ConfigManager
* @Date:2024/10/15 18:04
* @Describe: 读取jdbc配置文件的类
*/
public class ConfigManager {
//读取jdbc配置文件的类
private static String driver ;
private static String url;
private static String user;
private static String password;
static {//这里类加载是就读取到了参数
try {
ResourceBundle bundle = ResourceBundle.getBundle("com.lfq.config.jdbc");
driver = bundle.getString("driver");
url = bundle.getString("url");
user = bundle.getString("user");
password = bundle.getString ("password");
System.out.println("123");
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getDriver() {
return driver;
}
public static String getUrl() {
return url;
}
public static String getUser() {
return user;
}
public static String getPassword() {
return password;
}
}
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/news_dbms?characterEncoding=utf-8&serverTimezone=GMT%2B8 &useSSL=false
user=root
password=123456
3. BaseDao的封装
BaseDao
通常是一个数据访问对象(DAO)基类,提供数据库操作的基本方法,其他具体 DAO 类可以继承它并扩展特定的数据库操作功能。使用 BaseDao
的主要目的是将常用的数据库操作(如增删改查等)集中管理,减少重复代码,提高代码复用性。 也就是说它是最基础的Dao,所有的Dao应该去继承该BaseDao(很重要)
给大家演示一个通用的BaseDao的实例:
package com.lfq.util;
import java.sql.*;
/**
* @Author:lfq
* @Package:com.lfq.util
* @Filename:baseDao
* @Date:2024/10/15 18:04
* @Describe:
*/
@SuppressWarnings("all")
public class BaseDao {
//这里注意一下 咱们的参数通过上面的ConfigManager的静态方法直接获取轻松且便捷
private String driver = ConfigManager.getDriver();
private String url = ConfigManager.getUrl();
private String user = ConfigManager.getUser();
private String password = ConfigManager.getPassword();
public Connection conn=null;
public PreparedStatement psts = null; // 声明预处理数据库操作对象
public ResultSet rs= null;
public Connection getConn() throws Exception {
Class.forName(driver);
return DriverManager.getConnection(url,user,password);//返回连接对象
}
//自己创建一个过滤的executeUpdate()方法
public int executeUpadate(String sql,Object[] objects){
int num = 0;
try {
conn = getConn();//注册驱动 并获得连接对象
//对sql进行预处理 防止sql注入的发生
psts = conn.prepareStatement(sql);//预处理sql 获取预处理数据库对象
for (int i = 0; i < objects.length; i++) {
//遍历数组依次给占位符?传值
psts.setObject(i+1,objects[i]);
}
//执行sql
System.out.println(psts);//查看一下运行类型,属于那个类
//返回值为int 执行成功的记录行数
num = psts.executeUpdate();//返回结果
} catch (Exception e) {
e.printStackTrace();
} finally {
closeRes();
}
return num;
}
public ResultSet executeQuery(String sql,Object[] objs){
try {
conn = getConn();//注册驱动建立连接
psts = conn.prepareStatement(sql);//预处理sql 获取预处理数据库对象
if (objs!=null){
for (int i = 0; i < objs.length; i++) {
psts.setObject(i+1,objs[i]);
}
}
rs = psts.executeQuery();//执行sql
//这里我们取rs中的查询数据 并把数据封装给集合
} catch (Exception e) {
e.printStackTrace();
} finally {
}
return rs;
}
//自己创建一个关闭资源的公共方法
public void closeRes(){
if (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (psts!=null){
try {
psts.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
4. 以Dao模式实现增删改查
NewsDao:
package com.lfq.dao;
import com.lfq.pojo.News;
import com.lfq.util.PageUtil;
import java.util.List;
/**
* @Author:lfq
* @Package:com.lfq.dao
* @Filename:NewsDao
* @Date:2024/10/16 9:00
* @Describe:
*/
public interface NewsDao {
public int addNews(News news);
public int upDateNews(News news);
public int delNews(Integer id);
public News getNews(Integer id);
public List<News> selectAllNews();
}
NewsDaoImpl:
package com.lfq.dao.impl;
import com.lfq.dao.NewsDao;
import com.lfq.pojo.News;
import com.lfq.util.BaseDao;
import com.lfq.util.PageUtil;
import org.junit.jupiter.api.Test;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* @Author:lfq
* @Package:com.lfq.dao
* @Filename:NewsDao
* @Date:2024/10/15 9:59
* @Describe:
*/
public class NewsDaoImpl extends BaseDao implements NewsDao {//这里继承BaseDao
@Test
void info(){
System.out.println(System.getProperty("user.dir"));
}
@Test
public int addNews(News news){
System.out.println(System.getProperty("user.dir"));//输出工作目录 Test03
int num = 0;
//1.注册驱动
//2.获取连接对象
//3.进行预处理 获取预处理数据库操作对象(preparedStatement)
//给sql语句占位符?传值
//4.执行sql 并 接收结果
//5.处理查询结果集 只针对用select
String sql = "insert into news_detail(news_Name,content) values(?,?)";//?为占位符
//给占位符传值
Object [] objs = {news.getNew_Name(),news.getContent()};
num = executeUpadate(sql,objs);
System.out.println("成功添加了"+num+"行数据");
return num;
}
@Test
public int upDateNews(News news){
int num = 0;
String sql = "update news_detail set news_Name=?,content=? where news_Id=?";
Object[] objs = {news.getNew_Name(),news.getContent(),news.getNews_Id()};
num = executeUpadate(sql,objs);//这里调用咱们自己创建的工具类 用封装方法进行加工
System.out.println("成功修改了"+num+"行数据");
return num;
}
@Test
public int delNews(Integer id) {
int num = 0;
String sql = "delete from news_detail where news_Id = ?";
Object[] objects = {id};//有几个?就会有几个值
num = executeUpadate(sql, objects);
System.out.println("成功删除了" + num + "行数据");
return num;
}
@Override
public News getNews(Integer id) {
News news = null;
String sql = "select news_Id id,news_Name name,content,create_time from news_detail where news_Id=?";
Object[] objs = {id};
rs = executeQuery(sql, objs);
try {
while (rs.next()){
news = new News();//用序号的方式来获取查询数据 也可以用列名
news.setNews_Id(rs.getInt(1));
news.setNew_Name(rs.getString(2));
news.setContent(rs.getString(3));
news.setCreate_time(rs.getTimestamp(4));
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
closeRes();
}
return news;
}
public List<News> selectAllNews(){
//注册驱动
//获取连接对象
//创建预处理数据库操作对象
// stmt = conn.createStatement();
//执行查询sql并 接收查询结果
//5.处理查询的结果集 只针对用select
//
List<News> newslist = new ArrayList<>();
News news;
String sql = "select news_Id id,news_Name name,content,create_time from news_detail"; //查询语句后面可以不加;
Object [] objs = {};
try {
rs = executeQuery(sql, objs);
while (rs.next()){
news = new News();
news.setNews_Id(rs.getInt(1));
news.setNew_Name(rs.getString(2));
news.setContent(rs.getString(3));
System.out.println(rs.getTimestamp(4));
news.setCreate_time(rs.getTimestamp(4));//把日期转为datatime格式
newslist.add(news);//把news对象装进集合
}
System.out.println("-----查询结果-----");
for (News news1 : newslist) {//遍历输出查询结果
System.out.println(news1);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
closeRes();
}
return newslist;
}
}
我就不演示了 感兴趣的话 大家可以用Test测试一下。
5. 单例模式
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并为整个系统提供全局访问点。单例模式主要用于在系统中共享一些需要唯一实例的资源,例如数据库连接、日志系统配置、缓存对象等。
5.1. 单例模式的特点
- 唯一性:只能创建一个实例,任何调用者都获得同一个对象。
- 延迟初始化:通常使用延迟初始化的方式来创建实例,只有在第一次需要使用时才会创建,减少系统资源的开销。
- 全局访问点:通过静态方法提供对该实例的全局访问。
单例模式的实现有很多这里我们只介绍 饿汉式、懒汉式 。
5.2. 懒汉模式
在懒汉模式下,实例在第一次使用时才进行创建,因此称为"懒汉"------直到需要才"吃"。
特点:
- 延迟加载 :只有在首次调用
getInstance()
方法时才会创建实例。 - 线程不安全:如果不加同步控制,在多线程环境下可能会创建多个实例。
- 资源利用率高:只有在需要时才会创建对象,节省了资源。
适用场景:
- 当单例对象的创建和初始化操作较为复杂或者需要延迟加载时,可以考虑使用懒汉模式。
- 如果应用中频繁使用单例对象的情况不多,懒汉模式可以节省资源。
懒汉式(线程不安全):
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
注意 : 这种方式在多线程环境下可能会创建多个实例,因为instance
的赋值操作不是原子的。
懒汉式(线程安全)
public class SingletonLazyThreadSafe {
private static volatile SingletonLazyThreadSafe instance;
private SingletonLazyThreadSafe() {}
public static synchronized SingletonLazyThreadSafe getInstance() {
if (instance == null) {
instance = new SingletonLazyThreadSafe();
}
return instance;
}
// 或者使用双重检查锁定(Double-Checked Locking)来优化性能
public static SingletonLazyThreadSafe getInstanceOptimized() {
if (instance == null) {
synchronized (SingletonLazyThreadSafe.class) {
if (instance == null) {
instance = new SingletonLazyThreadSafe();
}
}
}
return instance;
}
}
5.3. 饿汉模式
在饿汉模式下,实例在类加载时就被创建,因此称为"饿汉"------因为它一开始就"吃饱了"。
特点:
- 线程安全:由于实例在类加载时就创建并初始化,所以不存在多线程环境下的线程安全问题。
- 简单:实现起来比较简单,没有复杂的同步控制。
- 性能较好:在访问量较大或者对性能有一定要求的场景下,由于不需要在获取实例时进行同步操作,性能较好。
适用场景:
-
当单例对象的创建和初始化操作比较简单,且在程序运行时就需要频繁使用时,可以考虑使用饿汉模式。
public class Singleton {
private static final Singleton instance = new Singleton();private Singleton() { // 私有构造方法 } public static Singleton getInstance() { return instance; }
}
5.4. 总结
- 饿汉模式适合在单例对象比较简单,且在程序整个生命周期内需要频繁使用的情况下,可以提升性能。
- 懒汉模式适合在单例对象创建和初始化较为复杂,或者需要延迟加载的情况下,以节省资源并避免不必要的初始化。
具体如何选择呢,像示例一中的代码,使用懒汉模式(带双重检查锁定)是比较合适的选择。因为:
- 延迟加载 :你的
JsonFormatter
类中的ObjectMapper
实例需要在第一次调用getInstance()
方法时才被初始化。这种延迟加载的方式可以节省资源,特别是在应用程序启动时,可能不立即需要操作 JSON 的情况下。 - 线程安全性 :通过双重检查锁定,确保了在多线程环境下只会创建一个
JsonFormatter
实例。这种方式在保证线程安全的同时,又能避免每次调用getInstance()
都进行同步,提高了性能。 - 资源利用 :由于
ObjectMapper
可能比较重量级(尤其是在配置了特定的序列化/反序列化规则时),懒汉模式可以避免不必要的对象创建和初始化,从而提高了资源的利用率。