在我展示的酒店赢博体育程序的第一个版本中,我们将赢博体育预订存储在一个文本文件中。现在我们知道了如何使用数据库,是时候更新Hotel赢博体育程序以将其赢博体育数据存储在MySQL中了。
第一步是使用MySQL工作台为我们的赢博体育程序创建数据库。数据库模式将被命名为“hotel”,它将有两个表。
第一张名为“rooms”的表存储了酒店会议室的基本信息。
列 | 类型 | 描述 |
---|---|---|
名字 | VARCHAR (32) | 房间的名字 |
能力 | INT | 空间能力 |
第二个表名为“reservations”,用于存储预订。
列 | 类型 | 描述 |
---|---|---|
id | INT | 预订的身份证号 |
客户 | INT | 客户的Id号 |
大小 | INT | 组的大小 |
一天 | 日期 | 预订日期 |
开始 | INT | 开始时间 |
持续时间 | INT | 持续时间(小时) |
房间 | VARCHAR (32) | 房间的名字 |
由于这仍然是一个FXML赢博体育程序,因此我们遵循制作FXML赢博体育程序的通常过程。要添加数据库支持,我们需要做两件事。第一种是在pom.xml中添加依赖项:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency>
第二步是在module-info.java中添加一行,表明我们将使用java中的一些类。sql包:
模块edu.lawrence.hotelsql{需要javafx.controls;需要javafx.fxml;需要java.sql;打开edu.lawrence.hotelsql到javafx.fxml;出口edu.lawrence.hotelsql;}
我在课堂上演示的与MySQL交互的机制足以用于相当简单的示例。由于在本例中我们处理的是一个稍微复杂一些的数据库,因此我们需要用一些更强大、更灵活的机制来取代一些简单的机制。
这里有一个例子。假设我们想要获取特定房间和日期的赢博体育预订。这里有一些展示了这样做的一种方法。
ObservableList<预订> get(LocalDate日期,字符串房间){ObservableList<预订>结果;results = FXCollections.observableArrayList();字符串查询= "select * from reservations where day =\'" + date.toString() + "\' " and room =\'" + room;try {ResultSet rSet = statement.executeQuery(查询);while(rSet.next()){//从rSet中读取数据,制作Reservation //对象,并将它们添加到结果中}}catch(SQLException ex) {ex. printstacktrace ();}返回结果;}
这个过程有些笨拙,而且容易出错。这里有几件事可能出错。一个是我们可能会忘记在SQL代码中的字符串等项周围加上引号。第二件事是,我们不能完全确定LocalDate toString()方法将生成与MySQL date类型期望看到的相匹配的日期表示。
完成赢博体育这些工作的另一种方法是使用Java PreparedStatement类。这个类允许我们构造包含占位符的SQL代码,然后使用方便的方法填充占位符。
下面是一些执行与上面相同查询的代码,但使用了PreparedStatement。
字符串查询=“select * from reservations where room=? ”和天= ?”;准备语句ps = connection.prepareStatement(查询);//启动查询…ps.setString(房间);ps.setDate (java.sql.Date.valueOf(日期));ResultSet rSet = ps.executeQuery();
为了在查询字符串中填充占位符,我们使用一系列set方法调用。每个set方法都有两个形参,一个是指定要填充哪个占位符的int,另一个是要替换到占位符中的一段数据。
这种方法更不容易出错。PreparedStatement类使用的set方法只允许特定的数据类型列表,并且这些数据类型与MySQL支持的数据类型紧密对应。例如,要为SQL DATE填充占位符,我们使用setDate(),它只接受java.sql.Date类作为其参数。这迫使我们在文档中查找将LocalDate对象转换为Date的方法。幸运的是,Date类允许这样做,并且使用这种转换可以保证我们将以正确的格式将日期信息发送到数据库。使用PreparedStatement方法的另一个好处是,我们不必担心引号之类的事情。PreparedStatement将自动在需要的任何地方为我们将它们放入SQL代码中。
数据库赢博体育程序中使用的一种常见设计模式是创建一个数据访问对象(DAO)作为数据库的网关。在这个赢博体育程序中,我们将有一个HotelDAO类来为我们管理赢博体育的数据库交互。该类将存储与数据库通信所需的赢博体育连接和语句对象。此外,DAO将记住用户在GUI中选择的房间和日期,因此我们可以在GUI中创建预订时正确保存预订。
下面几节将介绍DAO执行的一些关键任务。
HotelDAO需要维护到Hotel数据库的连接。建立该连接的明显位置是在DAO类的构造函数中:
public HotelDAO(){//加载数据库驱动程序try {Class.forName("com.mysql.cj.jdbc.Driver");} catch (ClassNotFoundException ex) {System.out。println(“无法加载数据库驱动程序”);} //打开连接并设置语句try {connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/hotel?user=student&password=Cmsc250!");statement = connection.createStatement();reservationsStmt = connection.prepareStatement(reservationsSQL);insertStmt = connection.prepareStatement(insertSQL);deleteStmt = connection.prepareStatement(deleteSQL);} catch (SQLException ex) {System.out。println(“无法连接到数据库”);ex.printStackTrace ();} reservations = FXCollections.observableArrayList();}
DAO将使用几个PreparedStatement对象,因此这个构造函数也是初始化这些对象的自然场所。
该类还有一个匹配的close()方法来关闭数据库连接。赢博体育程序的退出按钮的代码将调用这个:
Public void close() {try {connection.close();} catch (SQLException ex) {ex. printstacktrace ();}}
GUI包含允许用户指定他们想要使用的日期和房间的组件。DAO类可以存储这些信息以供以后使用。每当GUI中的天或房间发生变化时,主窗口的控制器将调用下面的方法。此方法记住日期和房间设置,并刷新该组合的预订列表。
公共无效setRoomAndDate(字符串房间,LocalDate日期){这。Room =房间;这一点。Date =日期;reservations.clear ();try{//首先查找该房间的容量ResultSet ResultSet = statement。executeQuery("select name=\"" + room + "\“”的房间容量);if (resultSet.next()) {capacity = resultSet.getInt(1);} //下一步搜索该房间和该日期的赢博体育预订。reservationsStmt。setString(房间);reservationsStmt。设置当前日期(java.sql.Date.valueOf(日期));resultSet = reservationsStmt.executeQuery();while (resultSet.next()){预订r =新预订();r.setIdNumber (resultSet.getInt (" id "));r.setCustomerNumber (resultSet.getInt(“客户”);r.setGroupSize (resultSet.getInt("大小"));int start = resultSet.getInt("start");r.setStart(开始);int end = start + resultSet.getInt(“duration”);r.setEnd(结束);reservations.add (r);}} catch (SQLException ex) {ex. printstacktrace ();}}
这里的代码使用一个准备好的语句进行选择,返回特定日期和房间的赢博体育预订。下面是准备好的语句的SQL代码。
private String reservationsSQL = “select * from reservations where room=? ”和天= ?”;
为了从这个查询返回的结果集中提取各个字段,我们利用了Java ResultSet对象允许我们使用字段名从给定行中获取字段的特性。
addReservation()方法将一个新的预订插入数据库。
公共无效的地址保留(保留新保留){尝试{插入stmt。setInt (newReservation.getCustomerNumber ());insertStmt。setInt (2, newReservation.getGroupSize ());insertStmt。设置当前日期(java.sql.Date.valueOf(日期));insertStmt。setInt (4, newReservation.getStart ());insertStmt。setInt(5, newReservation.getEnd() - newReservation.getStart());insertStmt。setString(房间);insertStmt.execute ();字符串查询= "select LAST_INSERT_ID()";ResultSet rset = statement.executeQuery(查询);if (rset.next()) {newReservation.setIdNumber(rset.getInt(1));}} catch (SQLException ex) {ex. printstacktrace ();} reservations.add (newReservation);FXCollections.sort(预订);}
这段代码使用一个准备好的语句来处理插入。下面是该语句的SQL代码。
private String insertSQL = “插入预订(客户、大小、日期、开始、持续时间、房间)值(?,?,?,?)”;
这里的一个复杂之处在于,当我们向数据库中插入一个新的Reservation时,我们指望MySQL自动为它分配一个id号。要了解id号是什么,我们必须运行第二个查询,该查询调用MySQL函数返回最后一次数据库插入的id号。
removeReservation()方法的作用是从数据库中删除一个预订。
公共无效removeReservation(保留删除){尝试{deleteStmt。setInt (toRemove.getIdNumber ());deleteStmt.execute ();} catch (SQLException ex) {ex. printstacktrace ();} reservations.remove (toRemove);}
这段代码使用一个准备好的语句来执行删除。下面是该语句的SQL代码。
private String deleteSQL = “删除id=?的预订”;
现在我们已经设置了一个DAO类来完成赢博体育的数据库交互,我们只需要修改赢博体育程序的其余部分来使用DAO。我们需要做的主要改变是在主窗口的控制器类中。我们需要添加一个HotelDAO对象作为这个控制器类的成员变量,并在控制器的initialize()方法中正确地初始化它。
public void initialize(URL URL, ResourceBundle rb) {dao = new HotelDAO();LocalDate = LocalDate.now();datePicker.setValue (selectedDate);ArrayList<String> roomNames = dao.getRoomNames();String room = roomNames.get(0);roomChoice.getItems () .addAll (roomNames);roomChoice.setValue(房间);.selectedItemProperty roomChoice.getSelectionModel()()。addListener((observable, oldValue, newValue)->setRoom(newValue));dao。setRoomAndDate(房间,selectedDate);reservationsList.setItems (dao.getReservations ());}
无论何时在GUI中发生任何更改日期或房间的事情,我们都需要将此更改通知DAO,并从中获取更新的Reservation对象列表。下面是链接到DatePicker的代码:当用户选择一个新日期时,将调用该代码。
@FXML私有无效setDate(ActionEvent事件){LocalDate selectedDate = datePicker.getValue();字符串房间= roomChoice.getSelectionModel().getSelectedItem().toString();dao.setRoomAndDate(房间,selectedDate);reservationsList.setItems (dao.getReservations ());}
同样,需要稍微修改一下对话框控制器的代码,以配合新的配置。控制器类将存储对HotelDAO的引用,并将直接与DAO对话以创建新的reservation。下面是更新为使用DAO的对话框控制器中的一个关键方法。
@FXML私有void acceptDialog(ActionEvent事件){int custNumber = Integer.parseInt(customerNumber.getText());int start = Integer.parseInt(startTime.getText());int end = Integer.parseInt(endTime.getText());int size = Integer.parseInt(groupSize.getText());预订newReservation =新预订(custNumber,dao.getDate(),开始,结束,大小,dao.getRoom());if(dao.allowsReservation(newReservation)) {dao. adreservation (newReservation);hide .getWindow customerNumber.getScene () () ();} else errorText。setext(“无法安排预订”);}