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]);

            }
            //执行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. 单例模式的特点

  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 可能比较重量级(尤其是在配置了特定的序列化/反序列化规则时),懒汉模式可以避免不必要的对象创建和初始化,从而提高了资源的利用率。
相关推荐
许灵均均7 分钟前
数据库视图
数据库
麻衣带我去上学29 分钟前
Spring源码学习(五):Spring AOP
java·学习·spring
北欧人写代码1 小时前
idea java 项目右键new file时 为什么是 kotlin class 不是普通class
java·kotlin·intellij-idea
笔墨登场说说1 小时前
JDK 里面的线程池和Tomcat线程池的区别
java·servlet·tomcat
on the way 1231 小时前
java.io.IOException: Too many open files
java·开发语言
m0_571957582 小时前
Java | Leetcode Java题解之第538题把二叉搜索树转换为累加树
java·leetcode·题解
CAORENZHU2 小时前
Java NIO 核心知识.下
java
程序员徐师兄2 小时前
基于 JavaWeb 的宠物商城系统(附源码,文档)
java·vue·springboot·宠物·宠物商城
engchina2 小时前
Neo4j数据库清理指南:如何安全地删除所有节点和索引
数据库·neo4j
小鸡脚来咯2 小时前
java 中List 的使用
java·开发语言