Java 中的事务脚本模式:使用合并脚本简化业务逻辑
也称为
- 脚本化事务
事务脚本设计模式的意图
Java 中的事务脚本模式通过过程来组织业务逻辑,每个过程处理来自表示层的单个请求。
使用真实示例详细解释事务脚本模式
真实示例
事务脚本设计模式的真实示例可以在银行出纳员的日常操作中看到。想象一家银行,每笔交易,例如存款、取款或账户之间转账,都由一个特定的脚本式过程处理。每个过程接收必要的详细信息(例如账户号码、金额)并以直接的、逐步的方式执行交易。这确保了每笔交易都得到正确且独立地处理,而无需复杂的规则和交互系统。这种简单、有条理的方法使银行出纳员能够在一天中有效地管理各种交易。
简单来说
事务脚本将业务逻辑组织成系统需要执行的事务。
维基百科说
事务脚本设计模式是组织应用程序中业务逻辑的一种直接方法,特别适合于表示层中的每个请求都可以由单个过程处理的场景。此模式通常用于简单应用程序或在快速开发和易于理解至关重要的系统中。每个事务脚本负责一项特定任务,例如处理订单或计算结果,并且通常直接与数据库交互。
Java 中事务脚本模式的编程示例
我们的 Java 中的事务脚本模式示例是关于预订酒店房间的。
Hotel
类负责预订和取消房间预订。
@Slf4j
public class Hotel {
private final HotelDaoImpl hotelDao;
public Hotel(HotelDaoImpl hotelDao) {
this.hotelDao = hotelDao;
}
public void bookRoom(int roomNumber) throws Exception {
Optional<Room> room = hotelDao.getById(roomNumber);
if (room.isEmpty()) {
throw new Exception("Room number: " + roomNumber + " does not exist");
} else {
if (room.get().isBooked()) {
throw new Exception("Room already booked!");
} else {
Room updateRoomBooking = room.get();
updateRoomBooking.setBooked(true);
hotelDao.update(updateRoomBooking);
}
}
}
public void cancelRoomBooking(int roomNumber) throws Exception {
Optional<Room> room = hotelDao.getById(roomNumber);
if (room.isEmpty()) {
throw new Exception("Room number: " + roomNumber + " does not exist");
} else {
if (room.get().isBooked()) {
Room updateRoomBooking = room.get();
updateRoomBooking.setBooked(false);
int refundAmount = updateRoomBooking.getPrice();
hotelDao.update(updateRoomBooking);
LOGGER.info("Booking cancelled for room number: " + roomNumber);
LOGGER.info(refundAmount + " is refunded");
} else {
throw new Exception("No booking for the room exists");
}
}
}
}
Hotel
类有两个方法,分别用于预订和取消房间。它们中的每一个都处理系统中的单个事务,使 Hotel
实现事务脚本模式。
bookRoom
方法整合了所有必要的步骤,例如检查房间是否已预订,如果未预订,则预订房间并使用 DAO 更新数据库。
cancelRoom
方法整合了以下步骤:检查房间是否已预订,如果已预订,则计算退款金额并使用 DAO 更新数据库。
这是包含用于运行示例的 main
方法的 App
类。
public class App {
private static final String H2_DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
public static void main(String[] args) throws Exception {
final var dataSource = createDataSource();
deleteSchema(dataSource);
createSchema(dataSource);
final var dao = new HotelDaoImpl(dataSource);
// Add rooms
addRooms(dao);
// Print room booking status
getRoomStatus(dao);
var hotel = new Hotel(dao);
// Book rooms
hotel.bookRoom(1);
hotel.bookRoom(2);
hotel.bookRoom(3);
hotel.bookRoom(4);
hotel.bookRoom(5);
hotel.bookRoom(6);
// Cancel booking for a few rooms
hotel.cancelRoomBooking(1);
hotel.cancelRoomBooking(3);
hotel.cancelRoomBooking(5);
getRoomStatus(dao);
deleteSchema(dataSource);
}
private static void getRoomStatus(HotelDaoImpl dao) throws Exception {
try (var customerStream = dao.getAll()) {
customerStream.forEach((customer) -> LOGGER.info(customer.toString()));
}
}
private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL);
}
}
private static void createSchema(DataSource dataSource) throws Exception {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL);
} catch (Exception e) {
throw new Exception(e.getMessage(), e);
}
}
private static DataSource createDataSource() {
var dataSource = new JdbcDataSource();
dataSource.setUrl(H2_DB_URL);
return dataSource;
}
private static void addRooms(HotelDaoImpl hotelDao) throws Exception {
for (var room : generateSampleRooms()) {
hotelDao.add(room);
}
}
private static List<Room> generateSampleRooms() {
final var room1 = new Room(1, "Single", 50, false);
final var room2 = new Room(2, "Double", 80, false);
final var room3 = new Room(3, "Queen", 120, false);
final var room4 = new Room(4, "King", 150, false);
final var room5 = new Room(5, "Single", 50, false);
final var room6 = new Room(6, "Double", 80, false);
return List.of(room1, room2, room3, room4, room5, room6);
}
}
App.java
文件包含应用程序的 main
入口点。它展示了在酒店管理环境中使用事务脚本模式。以下是有关其工作原理的分步说明
使用
createDataSource()
方法创建一个新的 H2 数据库数据源。此数据源用于与内存中的 H2 数据库交互。使用
deleteSchema(dataSource)
方法删除数据库中现有的模式(如果有)。这样做是为了确保应用程序启动前处于干净状态。使用
createSchema(dataSource)
方法在数据库中创建一个新模式。该模式包括应用程序所需的表和关系。创建一个
HotelDaoImpl
实例,它充当数据访问对象 (DAO)。此对象负责处理与酒店房间相关的数据库操作。使用
addRooms(dao)
方法将示例房间添加到酒店。此方法生成示例房间列表,并通过 DAO 将其添加到酒店。使用
getRoomStatus(dao)
方法打印所有房间的预订状态。使用 DAO 作为参数创建一个
Hotel
实例。此对象表示酒店,并提供预订和取消房间预订的方法。使用
hotel.bookRoom(roomNumber)
方法预订几个房间。然后使用
hotel.cancelRoomBooking(roomNumber)
方法取消部分预订。再次打印所有房间的预订状态以反映更改。
最后,使用
deleteSchema(dataSource)
方法再次删除数据库中的模式,清除数据库状态。
控制台输出
14:22:20.050 [main] INFO com.iluwatar.transactionscript.App -- Room(id=1, roomType=Single, price=50, booked=false)
14:22:20.051 [main] INFO com.iluwatar.transactionscript.App -- Room(id=2, roomType=Double, price=80, booked=false)
14:22:20.051 [main] INFO com.iluwatar.transactionscript.App -- Room(id=3, roomType=Queen, price=120, booked=false)
14:22:20.051 [main] INFO com.iluwatar.transactionscript.App -- Room(id=4, roomType=King, price=150, booked=false)
14:22:20.051 [main] INFO com.iluwatar.transactionscript.App -- Room(id=5, roomType=Single, price=50, booked=false)
14:22:20.051 [main] INFO com.iluwatar.transactionscript.App -- Room(id=6, roomType=Double, price=80, booked=false)
14:22:20.058 [main] INFO com.iluwatar.transactionscript.Hotel -- Booking cancelled for room number: 1
14:22:20.058 [main] INFO com.iluwatar.transactionscript.Hotel -- 50 is refunded
14:22:20.059 [main] INFO com.iluwatar.transactionscript.Hotel -- Booking cancelled for room number: 3
14:22:20.059 [main] INFO com.iluwatar.transactionscript.Hotel -- 120 is refunded
14:22:20.059 [main] INFO com.iluwatar.transactionscript.Hotel -- Booking cancelled for room number: 5
14:22:20.059 [main] INFO com.iluwatar.transactionscript.Hotel -- 50 is refunded
14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=1, roomType=Single, price=50, booked=false)
14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=2, roomType=Double, price=80, booked=true)
14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=3, roomType=Queen, price=120, booked=false)
14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=4, roomType=King, price=150, booked=true)
14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=5, roomType=Single, price=50, booked=false)
14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=6, roomType=Double, price=80, booked=true)
此模式适用于简单的业务逻辑,并且易于理解和维护。
何时在 Java 中使用事务脚本模式
- 在业务逻辑很简单并且可以轻松地组织成单独的过程时使用。
- 适用于具有简单事务要求的应用程序,或者逻辑不符合复杂架构(如领域模型)的应用程序。
事务脚本模式 Java 教程
Java 中事务脚本模式的实际应用
- 快速开发至关重要的早期创业公司和小型应用程序。
- 具有明确定义的程序的企业应用程序,例如银行交易或电子商务订单处理。
- 业务逻辑已作为脚本编写的遗留系统。
事务脚本模式的优点和权衡
优点
- 利用事务脚本模式可以增强代码简洁性并加快开发周期,尤其是在创业公司环境中。
- 简单且易于实现。
- 对于简单的业务逻辑易于理解和维护。
- 小型应用程序的快速开发周期。
权衡
- 如果管理不当,会导致代码重复。
- 不适用于复杂的业务逻辑;随着应用程序的增长,可能会变得难以管理。
- 与更结构化的方法(如领域模型)相比,隔离测试难度更大。
相关的 Java 设计模式
- 领域模型:与事务脚本不同,领域模型围绕数据模型组织业务逻辑,更适合复杂的业务规则。
- 服务层:通常与事务脚本一起使用来定义应用程序的边界并封装业务逻辑。
- 表模块:类似于事务脚本,但使用每个表一个类来组织逻辑,而不是每个请求一个过程。