在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象封装在一个公共API中。用程序设计的语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口在逻辑上对应这个特定的数据存储。
DAO模式是标准的J2EE设计模式之一.开发人员使用这个模式把底层的数据访问操作和上层的商务逻辑分开。一个典型的DAO实现有下列几个组件:
- 一个DAO工厂类;
- 一个DAO接口;
- 一个实现DAO接口的具体类;
- 数据传递对象(有些时候叫做值对象).
第一步,建立数据传递对象(pojo)Book实体类
package com.wise.tiger.pojo;
//********** import *************//
/**
* 图书实体类(在Dao模式中用于各层消息--数据传递)
*/
public class Book{
/**
* id
*/
private Integer id;
/**
* 图书名称
*/
private String title;
/**
* 图书作者
*/
private String author;
/**
* 图书价格
*/
private float price;
/**
* 出版社信息
*/
private String publisher;
/**
* 图书简介
*/
private String intro;
/**
* 出版日期
*/
private LocalDate publishDate = LocalDate.now();
//********************* setter and getter *******************//
}
sql脚本
drop database if exists db_book;
create database db_book;
use db_book;
create table tb_book(
id int primary key auto_increment,
title varchar(20) not null,
author varchar(20),
price decimal(10,2),
publisher varchar(30),
intro text,
publish_date date
)
一个DAO接口
package com.wise.tiger.dao;
//***************** import ******************//
/**
* 图书dao接口
* @Author: <a href="mailto:1020zhaodan@163.com">Adan</a>
* @Date: 2019/4/15 0015 10:59
* @version: 1.0-SNAPSHOT
*/
public interface BookDao {
/**
* 保存一本图书
* @param book 图书信息
*/
void persistent(Book book);
/**
* 根据id删除图书信息
* @param id id
*/
void delete(Integer id);
/**
* 修改图书信息
* @param book 修改后的图书信息
*/
void update(Book book);
/**
* 根据id获取图书信息
* @param id id
* @return 图书信息
*/
Book search(Integer id);
/**
* 加载全部图书信息,未分页
* @return 全部图书信息
*/
List<Book> search();
/**
* 获取图书总记录数
* @return 总记录数
*/
long getCount();
}
Dao接口具体实现类
package com.wise.tiger.dao.impl;
//***************** import ******************//
public class BookDaoImpl implements BookDao {
@Override
public void persistent(Book book) {
var sql = "INSERT INTO tb_book(title,author,price,publisher,intro,publish_date) VALUES (?,?,?,?,?,?)";
//采用try-with-resource语法确保相关资源的释放
try(var conn = DBHelper.getConnection();//1.通过工具类获取连接
var ptst = conn.prepareStatement(sql)){//2.预编译语句对象
//对预编译语句?占位符进行设置参数
ptst.setString(1,book.getTitle());
ptst.setString(2,book.getAuthor());
ptst.setFloat(3,book.getPrice());
ptst.setString(4,book.getPublisher());
ptst.setString(5,book.getIntro());
ptst.setString(6,book.getPublishDate());
//执行dml语句
ptst.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}
}
@Override
public void delete(Integer id) {
var sql = "DELETE FROM tb_book WHERE id = ?";
//采用try-with-resource语法确保相关资源的释放
try(var conn = DBHelper.getConnection();//1.通过工具类获取连接
var ptst = conn.prepareStatement(sql)){//2.预编译语句对象
//对预编译语句?占位符进行设置参数
ptst.setInt(1,id);
//执行dml语句
ptst.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}
}
@Override
public void update(Book book) {
var sql = "UPDATE tb_book SET title = ?,author=?,price=?,publisher=?,intro=?,publish_date=? WHERE id = ?";
//采用try-with-resource语法确保相关资源的释放
try(var conn = DBHelper.getConnection();//1.通过工具类获取连接
var ptst = conn.prepareStatement(sql)){//2.预编译语句对象
//对预编译语句?占位符进行设置参数
ptst.setString(1,book.getTitle());
ptst.setString(2,book.getAuthor());
ptst.setFloat(3,book.getPrice());
ptst.setString(4,book.getPublisher());
ptst.setString(5,book.getIntro());
ptst.setString(6,book.getPublishDate());
ptst.setInt(7,book.getId());
//执行dml语句
ptst.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}
}
@Override
public Book search(Integer id) {
Book book = null;
var sql = "SELECT id,title,author,price,publisher,intro,publish_date FROM tb_book WHERE id = ?";
//采用try-with-resource语法确保相关资源的释放
try(var conn = DBHelper.getConnection();//1.通过工具类获取连接
var ptst = conn.prepareStatement(sql)){//2.预编译语句对象
//对预编译语句?占位符进行设置参数
ptst.setInt(1,id);
//执行dql语句,返回结果集
var rs = ptst.executeQuery();
//处理结果集,结果集中列对应的值设置为book实体对应的属性值
if(rs.next()){
book = new Book();
book.setId(rs.getInt("id"));
book.setTitle(rs.getString("title"));
book.setAuthor(rs.getString("author"));
book.setPrice(rs.getFloat("price"));
book.setPublisher(rs.getString("publisher"));
book.setIntro(rs.getString("intro"));
book.setPublishDate(LocalDate.ofInstant(new Date(rs.getDate("publish_date").getTime()).toInstant(),ZoneId.systemDefault()));//java.sql.Date转LocalDate
}
}catch (SQLException e){
e.printStackTrace();
}
return book;
}
@Override
public List<Book> search() {
List<Book> books = new ArrayList<>();
var sql = "SELECT id,title,author,price,publisher,intro,publish_date FROM tb_book";
//采用try-with-resource语法确保相关资源的释放
try(var conn = DBHelper.getConnection();//1.通过工具类获取连接
var ptst = conn.prepareStatement(sql)){//2.预编译语句对象
//执行dql语句,返回结果集
var rs = ptst.executeQuery();
//处理结果集,结果集中列对应的值设置为book实体对应的属性值
while(rs.next()){
var book = new Book();
book.setId(rs.getInt("id"));
book.setTitle(rs.getString("title"));
book.setAuthor(rs.getString("author"));
book.setPrice(rs.getFloat("price"));
book.setPublisher(rs.getString("publisher"));
book.setIntro(rs.getString("intro"));
book.setPublishDate(LocalDate.ofInstant(new Date(rs.getDate("publish_date").getTime()).toInstant(),ZoneId.systemDefault()));//java.sql.Date转LocalDate
books.add(book);
}
}catch (SQLException e){
e.printStackTrace();
}
return books;
}
@Override
public long getCount() {
var count = 0;
var sql = "SELECT COUNT(1) FROM tb_book";
try(var conn = DBHelper.getConnection();//1.通过工具类获取连接
var ptst = conn.prepareStatement(sql)){//2.预编译语句对象
//执行dql语句,返回结果集
var rs = ptst.executeQuery();
//处理结果集,结果集中列对应的值设置为book实体对应的属性值
if(rs.next()){
count = rs.getLong(1);
}
}catch (SQLException e){
e.printStackTrace();
}
return count;
}
}
Dao工厂类(非必须):针对这个Dao可能有多个实现,我们需要根据其外部配置动态获取dao的实例,该工厂专门用于创建Dao实例
public class DaoFactory {
private static BookDao bookDao = null;
private static DaoFactory instance = new DaoFactory();
private DaoFactory() {
try {
Properties prop = new Properties();
InputStream inStream = DaoFactory.class.getClassLoader()
.getResourceAsStream("daoconfig.properties");
prop.load(inStream);
var bookDaoClass = prop.getProperty("bookDaoClass");
Class clazz = Class.forName(bookDaoClass);
bookDao = (BookDao) clazz.getDeclaredConstructor().newInstance();
} catch (Throwable e) {
throw new ExceptionInInitializerError(e);
}
}
public static DaoFactory getInstance() {
return instance;
}
public BookDao getBookDao() {
return bookDao;
}
}
相关配置文件
Java代码
bookDaoClass=com.wise.tiger.dao.impl.BookDaoImpl
数据源配置文件
driverClassName = com.mysql.cj.jdbc.Driver
url = jdbc:mysql:///db_book
username = root
password =
########## 连接池配置 #############
initialSize = 5
maxTotal = 20
maxIdle = 10
minIdle = 3
maxWaitMillis = 1000
connectionProperties = serverTimezone=UTC;useSSL=false;useUnicode=true;characterEncoding=utf-8
# 配置事务是否自动提交
# defaultAutoCommit = false
# 配置事务的格力级别 (READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE)
# defaultTransactionIsolation = REPEATABLE_READ
短字符指令集(枚举)
package com.wise.tiger.commons;
import java.util.HashMap;
import java.util.Map;
public enum CommandLineInput {
FIND_ALL('a'), FIND_BY_ID('f'), INSERT('i'), UPDATE('u'), DELETE('d'), EXIT('e');
private final static Map<Character, CommandLineInput> INPUTS;
static {
INPUTS = new HashMap<>();
for (CommandLineInput input : values()) {
INPUTS.put(input.getShortCmd(), input);
}
}
private final char shortCmd;
CommandLineInput(char shortCmd) {
this.shortCmd = shortCmd;
}
public char getShortCmd() {
return shortCmd;
}
public static CommandLineInput getCommandLineInputForInput(char input) {
return INPUTS.get(input);
}
}
工具类
package com.wise.tiger.commons;
// ****************** import ******************//
public class CommandLineInputHandler {
private BookService service = new BookServiceImplInDB();
public void printOptions() {
System.out.println("\n--- Book Application ---");
System.out.println("Please make a choice:");
System.out.println("(a)ll items");
System.out.println("(f)ind a specific item");
System.out.println("(i)nsert a new item");
System.out.println("(u)pdate an existing item");
System.out.println("(d)elete an existing item");
System.out.println("(e)xit");
}
public String readInput() {
Scanner scanner = new Scanner(System.in);
return scanner.nextLine();
}
public void processInput(CommandLineInput input) {
if (input == null) {
handleUnknownInput();
} else {
switch (input) {
case FIND_ALL:
printAllBookItems();
break;
case FIND_BY_ID:
printBookItem();
break;
case INSERT:
insertToDoItem();
break;
case UPDATE:
updateToDoItem();
break;
case DELETE:
deleteToDoItem();
break;
case EXIT:
break;
default:
handleUnknownInput();
}
}
}
private Integer askForItemId() {
System.out.println("Please enter the book ID:");
String input = readInput();
return Integer.parseInt(input);
}
private Book askForNewToDoAction() {
var book = new Book();
System.out.println("Please enter the title of the book:");
book.setTitle(readInput());
System.out.println("Please enter the author of the book:");
book.setAuthor(readInput());
System.out.println("Please enter the price of the book:");
book.setPrice(Float.parseFloat(readInput()));
System.out.println("Please enter the publisher of the book:");
book.setPublisher(readInput());
System.out.println("Please enter the intro of the book:");
book.setIntro(readInput());
return book;
}
private void printAllBookItems() {
var books = service.search();
if (books.isEmpty()) {
System.out.println("没有图书,赶快去添加一本吧!");
} else {
for (var book : books)
System.out.println(book);
}
}
private void printBookItem() {
var book = findBookItem();
if (book != null) {
System.out.println(book);
}
}
private Book findBookItem() {
Integer id = askForItemId();
var book = service.search(id);
if (book == null)
System.err.println("book item with ID " + id + " could not be found.");
return book;
}
private void insertToDoItem() {
var book = askForNewToDoAction();
service.save(book);
System.out.println("Successfully inserted book item .");
}
private void updateToDoItem() {
var book = findBookItem();
if (book != null) {
System.out.println(book);
System.out.println("Please enter the title of the book:");
book.setTitle(readInput());
System.out.println("Please enter the author of the book:");
book.setAuthor(readInput());
System.out.println("Please enter the price of the book:");
book.setPrice(Float.parseFloat(readInput()));
System.out.println("Please enter the publisher of the book:");
book.setPublisher(readInput());
System.out.println("Please enter the intro of the book:");
book.setIntro(readInput());
service.merge(book);
System.out.println("Successfully updated book item with ID " + book.getId() + ".");
}
}
private void deleteToDoItem() {
var book = findBookItem();
if (book != null) {
service.remove(book.getId());
System.out.println("Successfully deleted book item with ID " + book.getId() + ".");
}
}
private void handleUnknownInput() {
System.out.println("Please select a valid option!");
}
}
DBHelper工具类,用于获取连接等(后续添加事务控制)
package com.wise.tiger.commons;
// ****************** import ******************//
public class DBHelper {
private static DataSource dataSource;
private DBHelper(){
}
static {
try {
var prop = new Properties();
prop.load(DBHelper.class.getClassLoader().getResourceAsStream("dbcp-config.properties"));
dataSource = BasicDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接对象
* @return Connection
* @throws SQLException 获取连接失败异常
*/
public static Connection getConnection() throws SQLException{
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw e;
}
}
/**
* 返回数据源DataSource
* @return DataSource
*/
public static DataSource getDataSource() {
return dataSource;
}
}
程序入口
package com.wise.tiger.client;
//************ import ********************/
/**
* 程序入口
*/
public class BookApp {
public static final char DEFAULT_INPUT = '\u0000';
public static void main(String args[]) {
CommandLineInputHandler commandLineInputHandler = new CommandLineInputHandler();
char command = DEFAULT_INPUT;
while (CommandLineInput.EXIT.getShortCmd() != command) {
commandLineInputHandler.printOptions();
String input = commandLineInputHandler.readInput();
char[] inputChars = input.length() == 1 ? input.toCharArray() : new char[]{DEFAULT_INPUT};
command = inputChars[0];
CommandLineInput commandLineInput = CommandLineInput.getCommandLineInputForInput(command);
commandLineInputHandler.processInput(commandLineInput);
}
}
}
在该应用中省略了service层,后续事务控制中再进行添加。
至此,一个DAO设计模式就基本完成,但是这里面存在一个问题,就是大量的重复操作以及重复代码,这样的代码不便于我们阅读以及维护,或者由于开发者本身的疏忽而造成sql代码失控,如何解决这一问题呢?在下一节中我们采用模板方法模式以及策略模式对dao加以完善