Compare commits

..

7 Commits

Author SHA1 Message Date
Marc Becker
481a05e036 Adapt checkpoint to exercise update 2020-02-14 15:18:56 +00:00
Matthias Braun
296ef8b43e Revert "adapted current versions for DKOM Israel"
This reverts commit 7619bb43e8.
2020-01-14 20:02:44 +01:00
Matthias Braun
7619bb43e8 adapted current versions for DKOM Israel
* cds runtime 1.0.1 -> 1.1.0
* cds4j 1.2.1 -> 1.5.1
* spring -> 2.2.1.RELEASE
* sqlite datasource path to bookstore directory
2020-01-14 19:51:47 +01:00
Marc Becker
113ff266a7 Set CDS version to 3.17.4 in bookstore 2019-10-02 10:11:06 +00:00
Marc Becker
8e55780c5a Set CDS version to 3.17.4 in products-service 2019-10-02 10:10:36 +00:00
Adrian Görler
654aebfdc7 Use SQL timestamp literal format
Only the SQL timestamp literal format works with SQLite on JavaScript and Java
as well as with HANA.
2019-09-23 16:13:47 +02:00
Adrian Görler
e0ad269b49 Add createdAt to CSV files
Since @sap/cds 3.17.4 the cds deploy command automatically inserts values for createdAt. Unfortunately ISO timestamps are inserted, which cannot be read from Java.

This change adds values for createdAt that are compatible with Java.
2019-09-23 14:37:50 +02:00
11 changed files with 1 additions and 565 deletions

View File

@@ -1,7 +0,0 @@
---
applications:
- name: bookstore
path: srv/target/bookstore-1.0-SNAPSHOT.jar
random-route: true
services:
- bookstore-hana

View File

@@ -11,11 +11,9 @@
}, },
"dependencies": { "dependencies": {
"@sap/cds": "3.21.2", "@sap/cds": "3.21.2",
"@sap/capire-products": "file:sap-capire-products-1.0.0.tgz", "@sap/capire-products": "file:sap-capire-products-1.0.0.tgz"
"hdb": "^0.17.1"
}, },
"devDependencies": { "devDependencies": {
"@sap/hdi-deploy": "3.7.0",
"sqlite3": "^4.1.1" "sqlite3": "^4.1.1"
}, },
"cds": { "cds": {

View File

@@ -21,25 +21,10 @@
<artifactId>cds-starter-spring-boot-odata</artifactId> <artifactId>cds-starter-spring-boot-odata</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-feature-hana</artifactId>
</dependency>
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-feature-cloudfoundry</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.xerial</groupId> <groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId> <artifactId>sqlite-jdbc</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -1,105 +0,0 @@
package com.sap.teched.cap.bookstore.handlers;
import java.math.BigDecimal;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.Update;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnUpdate;
import com.sap.cds.services.ErrorStatuses;
import com.sap.cds.services.ServiceException;
import com.sap.cds.services.cds.CdsService;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.After;
import com.sap.cds.services.handler.annotations.Before;
import com.sap.cds.services.handler.annotations.ServiceName;
import com.sap.cds.services.persistence.PersistenceService;
import cds.gen.ordersservice.OrderItems;
import cds.gen.ordersservice.Orders;
import cds.gen.sap.capire.bookstore.Books;
import cds.gen.sap.capire.bookstore.Books_;
import cds.gen.sap.capire.bookstore.OrderItems_;
@Component
@ServiceName("OrdersService")
public class OrdersService implements EventHandler {
@Autowired
PersistenceService db;
@Before(event = CdsService.EVENT_CREATE, entity = "OrdersService.OrderItems")
public void validateBookAndDecreaseStock(List<OrderItems> items) {
for (OrderItems item : items) {
String bookId = item.getBookId();
Integer amount = item.getAmount();
// check if the book that should be ordered is existing
CqnSelect sel = Select.from(Books_.class).columns(b -> b.stock()).where(b -> b.ID().eq(bookId));
Books book = db.run(sel).first(Books.class).orElseThrow(() -> new ServiceException(ErrorStatuses.NOT_FOUND, "Book does not exist"));
// check if order could be fulfilled
int stock = book.getStock();
if (stock < amount) {
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Not enough books on stock");
}
// update the book with the new stock, means minus the order amount
book.setStock(stock - amount);
CqnUpdate update = Update.entity(Books_.class).data(book).where(b -> b.ID().eq(bookId));
db.run(update);
}
}
@Before(event = CdsService.EVENT_CREATE, entity = "OrdersService.Orders")
public void validateBookAndDecreaseStockViaOrders(List<Orders> orders) {
for(Orders order : orders) {
if(order.getItems() != null) {
validateBookAndDecreaseStock(order.getItems());
}
}
}
@After(event = { CdsService.EVENT_READ, CdsService.EVENT_CREATE }, entity = "OrdersService.OrderItems")
public void calculateNetAmount(List<OrderItems> items) {
for (OrderItems item : items) {
String bookId = item.getBookId();
// get the book that was ordered
CqnSelect sel = Select.from(Books_.class).where(b -> b.ID().eq(bookId));
Books book = db.run(sel).single(Books.class);
// calculate and set net amount
item.setNetAmount(book.getPrice().multiply(new BigDecimal(item.getAmount())));
}
}
@After(event = { CdsService.EVENT_READ, CdsService.EVENT_CREATE }, entity = "OrdersService.Orders")
public void calculateTotal(List<Orders> orders) {
for (Orders order : orders) {
// calculate net amount for expanded items
if(order.getItems() != null) {
calculateNetAmount(order.getItems());
}
// get all items of the order
CqnSelect selItems = Select.from(OrderItems_.class).where(i -> i.parent().ID().eq(order.getId()));
List<OrderItems> allItems = db.run(selItems).listOf(OrderItems.class);
// calculate net amount of all items
calculateNetAmount(allItems);
// calculate and set the orders total
BigDecimal total = new BigDecimal(0);
for(OrderItems item : allItems) {
total = total.add(item.getNetAmount());
}
order.setTotal(total);
}
}
}

View File

@@ -1,165 +0,0 @@
package com.sap.teched.cap.bookstore.handlers;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import cds.gen.sap.capire.bookstore.OrderItems;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class orderServiceTest {
private static final String ODATA_V4 = "/odata/v4";
private static final String BOOKS_URI = ODATA_V4 + "/BooksService/Books";
private static final String ORDERS_URI = ODATA_V4 + "/OrdersService/Orders";
private static final String ORDER_ITEMS_URI = ODATA_V4 + "/OrdersService/OrderItems";
@Autowired
private MockMvc mockMvc;
@Test
public void testGetBooks() throws Exception {
mockMvc.perform(get(BOOKS_URI).content("")).andExpect(status().isOk())
.andExpect(jsonPath("$.value[0].title", is("Wuthering Heights")));
}
@Test
public void testCreateOrderItem() throws Exception {
final String data = "{\"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\"book_ID\": \"abed2f7a-c50e-4bc5-89fd-9a00a54b4b16\", \"amount\": 1 }";
mockMvc.perform(post(ORDER_ITEMS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(get(ORDER_ITEMS_URI).content("")).andExpect(status().isOk())
.andExpect(jsonPath("$.value[0].parent_ID", is("12345678-aaaa-bbbb-dddd-ddddeeeeffef")));
}
@Test
public void tesValidateBookAndDecreaseStock() throws Exception {
final String data = "{\"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffee\",\"book_ID\": \"5e69a718-d86b-4461-8953-0edadcd87960\", \"amount\": 20 }";
mockMvc.perform(post(ORDER_ITEMS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(post(ORDER_ITEMS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isBadRequest());
}
@Test
public void tesCalculateNetAmount() throws Exception {
final String data = "{\"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffbb\",\"book_ID\": \"b7bca6dd-0497-465e-9a5a-56f244174c8c\", \"amount\": 10 }";
MvcResult result = mockMvc.perform(post(ORDER_ITEMS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andReturn();
JSONObject jsonObj = new JSONObject(result.getResponse().getContentAsString());
String id = jsonObj.get("ID").toString();
mockMvc.perform(get(ORDER_ITEMS_URI+ "(" + id + ")").content("")).andExpect(status().isOk())
.andExpect(jsonPath("$.netAmount", is(141.4)));
}
@Test
public void testCreateOrder() throws Exception {
final String orderData = "{\r\n" +
" \"items\": [{\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"abed2f7a-c50e-4bc5-89fd-9a00a54b4b16\",\r\n" +
" \"amount\": 1\r\n" +
" }, {\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"b7bca6dd-0497-465e-9a5a-56f244174c8c\",\r\n" +
" \"amount\": 1\r\n" +
" }]\r\n" +
"}";
mockMvc.perform(post(ORDERS_URI).contentType(APPLICATION_JSON_UTF8).content(
orderData)) .andExpect(status().isCreated());
}
@Test
public void testCalculateTotal() throws Exception {
final String orderData = "{\r\n" +
" \"items\": [{\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"abed2f7a-c50e-4bc5-89fd-9a00a54b4b16\",\r\n" +
" \"amount\": 1\r\n" +
" }, {\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"b7bca6dd-0497-465e-9a5a-56f244174c8c\",\r\n" +
" \"amount\": 2\r\n" +
" }]\r\n" +
"}";
MvcResult result = mockMvc.perform(post(ORDERS_URI).contentType(APPLICATION_JSON_UTF8).content(orderData))
.andReturn();
JSONObject jsonObj = new JSONObject(result.getResponse().getContentAsString());
String id = jsonObj.get("ID").toString();
mockMvc.perform(get(ORDERS_URI+ "(" + id + ")").content("")).andExpect(status().isOk())
.andExpect(jsonPath("$.total", is(39.39)));
}
@Test
public void testValidateBookAndDecreaseStockViaOrders() throws Exception {
final String orderData = "{\r\n" +
" \"items\": [{\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"fd0c5fda-8811-4e20-bcff-3a776abc290a\",\r\n" +
" \"amount\": 300\r\n" +
" }, {\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"b7bca6dd-0497-465e-9a5a-56f244174c8c\",\r\n" +
" \"amount\": 1\r\n" +
" }]\r\n" +
"}";
mockMvc.perform(post(ORDERS_URI).contentType(APPLICATION_JSON_UTF8).content(orderData))
.andExpect(status().isCreated());
mockMvc.perform(post(ORDERS_URI).contentType(APPLICATION_JSON_UTF8).content(orderData))
.andExpect(status().isBadRequest());
}
}

View File

@@ -1,82 +0,0 @@
package com.sap.teched.cap.bookstore.testUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sap.cds.ql.Insert;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsSimpleType;
import com.sap.cds.services.persistence.PersistenceService;
public class CsvUploader {
private final CdsModel model;
private final PersistenceService ds;
public CsvUploader(CdsModel model, PersistenceService ds) {
this.model = model;
this.ds = ds;
}
void insertContent(File file) throws FileNotFoundException, IOException {
String fileName = file.getName();
String entityName = fileName.replace("-", ".").substring(0, fileName.length() - 4);
CdsEntity entity = model.getEntity(entityName);
List<Map<String, Object>> data = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line = br.readLine();
CdsElement[] headers = getHeaders(entity, line);
while ((line = br.readLine()) != null) {
Map<String, Object> row = new HashMap<>();
String[] values = line.split(";");
for (int i = 0; i < headers.length; i++) {
CdsElement element = headers[i];
String typeName = element.getType().as(CdsSimpleType.class).getQualifiedName();
final Object value;
String txt = values[i];
if ("".equals(txt)) {
continue;
}
switch (typeName) {
case "cds.Integer":
value = Integer.valueOf(txt);
break;
case "cds.Date":
value = LocalDate.parse(txt);
break;
default:
value = txt;
}
row.put(element.getName(), value);
}
data.add(row);
}
}
Insert insert = Insert.into(entity).entries(data);
ds.run(insert);
}
private CdsElement[] getHeaders(CdsEntity entity, String line) {
String[] elementNames = line.split(";");
CdsElement[] result = new CdsElement[elementNames.length];
for (int i = 0; i < elementNames.length; i++) {
result[i] = entity.getElement(elementNames[i]);
}
return result;
}
}

View File

@@ -1,45 +0,0 @@
package com.sap.teched.cap.bookstore.testUtil;
import java.io.File;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import com.sap.cds.services.persistence.PersistenceService;
import com.sap.cds.services.runtime.CdsRuntime;
@Component
public class DataLoader implements ApplicationRunner {
@Autowired
private CdsRuntime runtime;
public void run(ApplicationArguments args) {
PersistenceService ds = (PersistenceService) runtime.getServiceCatalog()
.getService(PersistenceService.DEFAULT_NAME);
runtime.runInRequestContext(null, r -> {
runtime.runInChangeSetContext(c -> {
File folder = new File("../db/data");
CsvUploader uploader = new CsvUploader(runtime.getCdsModel(), ds);
for (File file : folder.listFiles()) {
try {
if(file.getName().equals("sap.capire.bookstore-Books_texts.csv"))
continue;
uploader.insertContent(file);
} catch (RuntimeException | IOException e) {
throw new RuntimeException("exception reading file " + file.getName(), e);
}
}
return null;
});
return null;
});
}
}

View File

@@ -1,7 +0,0 @@
---
spring:
profiles: default
datasource:
url: "jdbc:sqlite::memory:"
driver-class-name: org.sqlite.JDBC
initialization-mode: always

View File

@@ -25,11 +25,6 @@
<groupId>org.xerial</groupId> <groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId> <artifactId>sqlite-jdbc</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -1,124 +0,0 @@
package com.sap.teched.cap.productsservice;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class productServiceTest {
private static final String ODATA_V4 = "/odata/v4";
private static final String PRODUCTS_URI = ODATA_V4 + "/AdminService/Products";
private static final String CATEGORIES_URI = ODATA_V4 + "/AdminService/Categories";
private static final String CURRENCIES_URI = ODATA_V4 + "/AdminService/Currencies";
@Autowired
private MockMvc mockMvc;
@Test
public void testProducts() throws Exception {
mockMvc.perform(get(PRODUCTS_URI).content("")).andExpect(status().isOk());
}
@Test
public void testPostProducts() throws Exception {
final String data = "{\"ID\": \"12345678-aaaa-bbbb-cccc-ddddeeeeffff\", \"title\": \"Awesome product\", \"descr\": \"It is really really awesome!\"}";
mockMvc.perform(post(PRODUCTS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
}
@Test
public void testGetProduct() throws Exception {
final String id = "12345611-aaaa-bbbb-cccc-ddddeeeeffef";
final String data = "{\"ID\": \"12345611-aaaa-bbbb-cccc-ddddeeeeffef\", \"title\": \"Awesome book\", \"descr\": \"It is an awesome book!\"}";
mockMvc.perform(post(PRODUCTS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(get(PRODUCTS_URI + "(" + id + ")").content("")).andExpect(status().isOk())
.andExpect(jsonPath("$.title", is("Awesome book")));
}
@Test
public void testEditProduct() throws Exception {
final String id = "12345678-aaaa-bbbb-dddd-ddddeeeeffef";
final String data = "{\"ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\", \"title\": \"Awesome book\", \"descr\": \"It is an awesome book!\"}";
final String changedData = "{\"ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\", \"title\": \"Awesome book\", \"descr\": \"It is a good book! I love it\"}";
mockMvc.perform(post(PRODUCTS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(put(PRODUCTS_URI + "(" + id + ")").contentType(APPLICATION_JSON_UTF8).content(changedData))
.andExpect(status().isOk()).andExpect(jsonPath("$.modifiedAt", is(not(nullValue()))));
}
@Test
public void testDeleteProduct() throws Exception {
final String id = "12345678-aaaa-bbbb-cccc-ddddeeeeffdd";
final String data = "{\"ID\": \"12345678-aaaa-bbbb-cccc-ddddeeeeffdd\", \"title\": \"Bad book\", \"descr\": \"It is a very bad book!\"}";
mockMvc.perform(post(PRODUCTS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(delete(PRODUCTS_URI + "(" + id + ")").content("")).andExpect(status().isNoContent());
mockMvc.perform(get(PRODUCTS_URI + "(" + id + ")").content("")).andExpect(status().isNotFound());
}
@Test
public void testGetProductWhenProductIDNotPresent() throws Exception {
final String data = "{\"ID\": \"12345678-aaaa-bbbb-cccc-ddddeeeeffaa\", \"title\": \"Another book\", \"descr\": \"It is bad book!\"}";
mockMvc.perform(post(PRODUCTS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(get(PRODUCTS_URI + "(123)").content("")).andExpect(status().isBadRequest());
}
@Test
public void testPostCategories() throws Exception {
final String data = "{\"ID\": 2, \"parent_ID\": 1, \"name\": \"Fiction\"}";
mockMvc.perform(post(CATEGORIES_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
}
@Test
public void testEditCategories() throws Exception {
final String data = "{\"ID\": 3, \"parent_ID\": 1, \"name\": \"Fiction\"}";
final String changedData = "{\"ID\": 3, \"name\": \"Science Fiction\"}";
mockMvc.perform(post(CATEGORIES_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(put(CATEGORIES_URI + "(3)").contentType(APPLICATION_JSON_UTF8).content(changedData))
.andExpect(status().isOk());
}
@Test
public void testPostCurrencies() throws Exception {
final String data = "{\"code\": \"USD\", \"symbol\": \"$\", \"name\": \"US Dollar\", \"descr\": \"United States Dollar\"}";
mockMvc.perform(post(CURRENCIES_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
}
}

View File

@@ -1,7 +0,0 @@
---
spring:
profiles: default
datasource:
url: "jdbc:sqlite::memory:"
driver-class-name: org.sqlite.JDBC
initialization-mode: always