欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > DAO模式及单例模式

DAO模式及单例模式

2025/4/20 12:16:35 来源:https://blog.csdn.net/qq_63533204/article/details/143332193  浏览:    关键词:DAO模式及单例模式

学习本节需要用到的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]);}//执行sqlSystem.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@Testvoid  info(){System.out.println(System.getProperty("user.dir"));}@Testpublic  int   addNews(News news){System.out.println(System.getProperty("user.dir"));//输出工作目录 Test03int num = 0;//1.注册驱动//2.获取连接对象//3.进行预处理 获取预处理数据库操作对象(preparedStatement)//给sql语句占位符?传值//4.执行sql 并 接收结果//5.处理查询结果集  只针对用selectString 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;}@Testpublic 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;}@Testpublic 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;}@Overridepublic 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. 单例模式的特点

  1. 唯一性:只能创建一个实例,任何调用者都获得同一个对象。
  2. 延迟初始化:通常使用延迟初始化的方式来创建实例,只有在第一次需要使用时才会创建,减少系统资源的开销。
  3. 全局访问点:通过静态方法提供对该实例的全局访问。

单例模式的实现有很多这里我们只介绍 饿汉式、懒汉式 。

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. 总结

  • 饿汉模式适合在单例对象比较简单,且在程序整个生命周期内需要频繁使用的情况下,可以提升性能。
  • 懒汉模式适合在单例对象创建和初始化较为复杂,或者需要延迟加载的情况下,以节省资源并避免不必要的初始化。

具体如何选择呢,像示例一中的代码,使用懒汉模式(带双重检查锁定)是比较合适的选择。因为:

  1. 延迟加载:你的 JsonFormatter 类中的 ObjectMapper 实例需要在第一次调用 getInstance() 方法时才被初始化。这种延迟加载的方式可以节省资源,特别是在应用程序启动时,可能不立即需要操作 JSON 的情况下。
  2. 线程安全性:通过双重检查锁定,确保了在多线程环境下只会创建一个 JsonFormatter 实例。这种方式在保证线程安全的同时,又能避免每次调用 getInstance() 都进行同步,提高了性能。
  3. 资源利用:由于 ObjectMapper 可能比较重量级(尤其是在配置了特定的序列化/反序列化规则时),懒汉模式可以避免不必要的对象创建和初始化,从而提高了资源的利用率。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词