Skip to content

Commit 3454168

Browse files
authored
Sessionless txns demo updates (#443)
* Fix concurrency tests * Use a stored procedure to query and lock seats * Give user create procedure privilidge * Remove semicolon from query * replace 1 with true
1 parent 1f64c2e commit 3454168

File tree

8 files changed

+76
-31
lines changed

8 files changed

+76
-31
lines changed

java/jdbc/SessionlessTransactions/src/main/java/com/oracle/jdbc/samples/sessionlesstxns/service/BookingService.java

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,21 @@
2020
import com.oracle.jdbc.samples.sessionlesstxns.util.Util;
2121
import oracle.jdbc.OracleConnection;
2222
import oracle.jdbc.OraclePreparedStatement;
23+
import oracle.jdbc.OracleTypes;
2324
import org.springframework.stereotype.Service;
2425

2526
import javax.sql.DataSource;
27+
import java.math.BigDecimal;
28+
import java.sql.Array;
29+
import java.sql.CallableStatement;
2630
import java.sql.Connection;
2731
import java.sql.PreparedStatement;
2832
import java.sql.ResultSet;
2933
import java.sql.SQLException;
3034
import java.sql.Savepoint;
3135
import java.sql.Timestamp;
3236
import java.util.ArrayList;
37+
import java.util.Arrays;
3338
import java.util.List;
3439

3540
@Service
@@ -175,7 +180,7 @@ private List<Long> lockAndBookSeats(OracleConnection conn, long bookingId, long
175180
private void saveReceipt(OracleConnection conn, String receiptNumber, double sum, long bookingId, long paymentMethodId)
176181
throws SQLException {
177182
final String saveReceiptDML = """
178-
INSERT INTO receipts (created_at, receipt_number, total, booking_id, payment_method_id) values (?, ?, ?, ?, ?);
183+
INSERT INTO receipts (created_at, receipt_number, total, booking_id, payment_method_id) values (?, ?, ?, ?, ?)
179184
""";
180185

181186
try (OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement(saveReceiptDML)) {
@@ -243,20 +248,24 @@ private void removeTicket(OracleConnection conn, long seatId, long bookingId)
243248
* availability of the tickets in the database.
244249
*/
245250
private List<Long> getFreeSeats(OracleConnection conn, long flightId, int count) throws SQLException {
246-
final String getFreeSeatsQuery = """
247-
SELECT id FROM seats
248-
WHERE available = true AND flight_id = ?
249-
FETCH FIRST ? ROW ONLY
250-
FOR UPDATE SKIP LOCKED;""";
251+
final String procedureCall = "{call fetch_seats(?, ?, ?)}";
252+
List<Long> seats = null;
251253

252-
List<Long> seats = new ArrayList<>();
253-
254-
try(PreparedStatement stmt = conn.prepareStatement(getFreeSeatsQuery);) {
254+
try (CallableStatement stmt = conn.prepareCall(procedureCall)) {
255+
// Set input parameters
255256
stmt.setLong(1, flightId);
256257
stmt.setInt(2, count);
257-
ResultSet rs = stmt.executeQuery();
258-
while (rs.next()) {
259-
seats.add(rs.getLong(1));
258+
// Register the OUT parameter (Oracle NUMBER_TABLE)
259+
stmt.registerOutParameter(3, OracleTypes.ARRAY, "DBMS_SQL.NUMBER_TABLE");
260+
// Execute the procedure
261+
stmt.execute();
262+
263+
// Retrieve the results
264+
Array resultArray = stmt.getArray(3);
265+
266+
if (resultArray != null) {
267+
seats = Arrays.stream((BigDecimal[]) resultArray.getArray()).map(BigDecimal::longValue).toList();
268+
resultArray.free();
260269
}
261270
}
262271

java/jdbc/SessionlessTransactions/src/test/java/com/oracle/jdbc/samples/sessionlesstxns/TestApis.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,10 @@ void thirdScenario() {
144144
}
145145

146146
private void loadData() {
147-
runSQLScript("dataLoader.sql");
147+
runSQLScript("dataLoader.sql", ";");
148148
}
149149

150150
private void cleanTables() {
151-
runSQLScript("dataCleaner.sql");
151+
runSQLScript("dataCleaner.sql", ";");
152152
}
153153
}

java/jdbc/SessionlessTransactions/src/test/java/com/oracle/jdbc/samples/sessionlesstxns/TestBase.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
import org.springframework.test.context.ActiveProfiles;
2626

2727
import java.io.InputStream;
28+
import java.sql.Connection;
29+
import java.sql.ResultSet;
30+
import java.sql.SQLException;
31+
import java.sql.Statement;
2832
import java.util.List;
2933
import java.util.Scanner;
3034

@@ -54,10 +58,10 @@ void tearDown() {
5458
dropSchema();
5559
}
5660

57-
public void runSQLScript(String fileName) {
61+
public void runSQLScript(String fileName, String delimiter) {
5862
InputStream inputStream = TestBase.class.getClassLoader().getResourceAsStream(fileName);
59-
List<String> instructions = new Scanner(inputStream).useDelimiter(";").tokens().toList();
60-
jdbcTemplate.batchUpdate(instructions.toArray(new String[0]));
63+
List<String> instructions = new Scanner(inputStream).useDelimiter(delimiter).tokens().toList();
64+
jdbcTemplate.batchUpdate(instructions.toArray(new String[0]));
6165
}
6266

6367
public static StartTransactionResponse testAPIStartTransaction(int timeout, long flightId, int count, HttpStatus expectedStatus) {
@@ -149,10 +153,10 @@ public void testAPICancelBooking(String transactionId, HttpStatus expectedStatus
149153
}
150154

151155
private void createSchema() {
152-
runSQLScript("createSchema.sql");
156+
runSQLScript("createSchema.sql", ";/");
153157
}
154158

155159
private void dropSchema() {
156-
runSQLScript("dropSchema.sql");
160+
runSQLScript("dropSchema.sql", ";");
157161
}
158162
}

java/jdbc/SessionlessTransactions/src/test/java/com/oracle/jdbc/samples/sessionlesstxns/TestConcurrency.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616
import org.springframework.context.annotation.Primary;
1717
import org.springframework.http.HttpStatus;
1818

19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.concurrent.ExecutionException;
1922
import java.util.concurrent.ExecutorService;
2023
import java.util.concurrent.Executors;
24+
import java.util.concurrent.Future;
2125

2226
public class TestConcurrency extends TestBase {
2327

@@ -62,10 +66,20 @@ void testNormalScenario() {
6266

6367
final int numberOfExecutions = 50;
6468
try (ExecutorService exService = Executors.newFixedThreadPool(numberOfExecutions)) {
65-
for (int i=0; i<numberOfExecutions; i++) {
66-
exService.submit(test);
69+
List<Future<?>> futures = new ArrayList<>();
70+
for (int i = 0; i < numberOfExecutions; i++) {
71+
futures.add(exService.submit(test));
72+
}
73+
// Wait for all tasks to complete
74+
for (Future<?> future : futures) {
75+
try {
76+
future.get(); // This blocks until the task completes
77+
} catch (InterruptedException | ExecutionException e) {
78+
logger.error("Task execution failed", e);
79+
}
6780
}
6881
}
82+
6983
logger.info("Execution time: {} s", (System.currentTimeMillis() - start) / 1_000);
7084
}
7185

@@ -79,7 +93,7 @@ void testScenario() throws InterruptedException {
7993
Assertions.assertEquals(REQUESTED_SEATS1, startTransaction.seatIds().size());
8094

8195
Thread.sleep(1000);
82-
final int REQUESTED_SEATS2 = 2;
96+
final int REQUESTED_SEATS2 = 1;
8397
var requestTickets = testAPIRequestTickets(
8498
startTransaction.transactionId(), FLIGHT1_ID, REQUESTED_SEATS2, startTransaction.bookingId(), HttpStatus.CREATED);
8599

@@ -102,10 +116,10 @@ void testScenario() throws InterruptedException {
102116
}
103117

104118
private void loadData() {
105-
runSQLScript("hundredsSeats.sql");
119+
runSQLScript("hundredsSeats.sql", ";");
106120
}
107121

108122
private void cleanTables() {
109-
runSQLScript("dataCleaner.sql");
123+
runSQLScript("dataCleaner.sql", ";");
110124
}
111125
}

java/jdbc/SessionlessTransactions/src/test/resources/application.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ spring.datasource.oracleucp.sql-for-validate-connection=select * from dual
1414
spring.datasource.oracleucp.connection-pool-name=connectionPool-${random.uuid}
1515
spring.datasource.oracleucp.initial-pool-size=5
1616
spring.datasource.oracleucp.min-pool-size=1
17-
spring.datasource.oracleucp.max-pool-size=10
17+
spring.datasource.oracleucp.max-pool-size=10
18+
spring.datasource.oracleucp.connection-wait-timeout=120

java/jdbc/SessionlessTransactions/src/test/resources/createSchema.sql

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ CREATE TABLE bookings (
77
id NUMBER GENERATED ALWAYS AS IDENTITY,
88
created_at DATE,
99
CONSTRAINT BOOKING_PK PRIMARY KEY (id)
10-
);
10+
);/
1111

1212
CREATE TABLE flights (
1313
id NUMBER,
@@ -17,15 +17,15 @@ CREATE TABLE flights (
1717
departure TIMESTAMP,
1818
price NUMBER,
1919
CONSTRAINT FLIGHT_PK PRIMARY KEY (id)
20-
);
20+
);/
2121

2222
CREATE TABLE seats (
2323
id NUMBER,
2424
flight_id NUMBER NOT NULL,
2525
available BOOLEAN NOT NULL,
2626
CONSTRAINT SEAT_PK PRIMARY KEY (id),
2727
CONSTRAINT SEAT_FLIGHT_FK FOREIGN KEY (flight_id) REFERENCES flights(id)
28-
);
28+
);/
2929

3030
CREATE TABLE tickets (
3131
id NUMBER GENERATED ALWAYS AS IDENTITY,
@@ -34,12 +34,12 @@ CREATE TABLE tickets (
3434
CONSTRAINT TICKET_PK PRIMARY KEY (id),
3535
CONSTRAINT TICKET_SEATS_FK FOREIGN KEY (seat_id) REFERENCES seats(id),
3636
CONSTRAINT TICKET_BOOKINGS_FK FOREIGN KEY (booking_id) REFERENCES bookings(id)
37-
);
37+
);/
3838

3939
CREATE TABLE payment_methods (
4040
id NUMBER,
4141
CONSTRAINT PAYMENT_METHOD_PK PRIMARY KEY (id)
42-
);
42+
);/
4343

4444
CREATE TABLE receipts (
4545
id NUMBER GENERATED ALWAYS AS IDENTITY,
@@ -51,4 +51,19 @@ CREATE TABLE receipts (
5151
CONSTRAINT RECEIPT_PK PRIMARY KEY (id),
5252
CONSTRAINT RECEIPT_BOOKING_FK FOREIGN KEY (booking_id) REFERENCES bookings(id),
5353
CONSTRAINT RECEIPT_PAYMENT_M_FK FOREIGN KEY (payment_method_id) REFERENCES payment_methods(id)
54-
);
54+
);/
55+
56+
CREATE OR REPLACE PROCEDURE fetch_seats(
57+
f_id IN NUMBER,
58+
n_rows IN INT,
59+
t_data OUT DBMS_SQL.NUMBER_TABLE
60+
) AS
61+
CURSOR c IS
62+
SELECT id FROM seats
63+
WHERE available = true AND flight_id = f_id
64+
FOR UPDATE SKIP LOCKED;
65+
BEGIN
66+
OPEN c;
67+
FETCH c BULK COLLECT INTO t_data LIMIT n_rows;
68+
CLOSE c;
69+
END fetch_seats;

java/jdbc/SessionlessTransactions/src/test/resources/createUser.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ GRANT CREATE SESSION TO test_user;
88
GRANT CREATE TABLE TO test_user;
99
GRANT CREATE SEQUENCE TO test_user;
1010
GRANT DROP ANY TABLE TO test_user;
11+
GRANT CREATE PROCEDURE to test_user;
1112
GRANT UNLIMITED TABLESPACE TO test_user;

java/jdbc/SessionlessTransactions/src/test/resources/dropSchema.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
44
*/
55

6+
DROP PROCEDURE fetch_seats;
67
DROP TABLE receipts;
78
DROP TABLE payment_methods;
89
DROP TABLE tickets;

0 commit comments

Comments
 (0)