Add Spring Boot example
diff --git a/empire-db-examples/empire-db-example-spring-boot/README.md b/empire-db-examples/empire-db-example-spring-boot/README.md
new file mode 100644
index 0000000..f6817f6
--- /dev/null
+++ b/empire-db-examples/empire-db-example-spring-boot/README.md
@@ -0,0 +1,13 @@
+# Empire-DB Spring Boot Example
+
+This example uses the convenience configuration options provided by Spring Boot.
+Instead of `config.xml` all configuration can be found in `application.yml`.
+
+This example builds an executable JAR you can build and execute from this folder like this:
+
+```sh
+$ mvn clean install
+$ java -jar target/empire-db-example-spring-boot-3.0.0-SNAPSHOT.jar
+```
+
+An embedded hsqldb is used by default.
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-spring-boot/pom.xml b/empire-db-examples/empire-db-example-spring-boot/pom.xml
new file mode 100644
index 0000000..59b1132
--- /dev/null
+++ b/empire-db-examples/empire-db-example-spring-boot/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>2.6.3</version>
+ <relativePath/> <!-- lookup parent from repository -->
+ </parent>
+
+ <name>Apache Empire-db Spring Boot Example</name>
+ <groupId>org.apache.empire-db</groupId>
+ <artifactId>empire-db-example-spring-boot</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.empire-db</groupId>
+ <artifactId>empire-db</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-jdbc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.hsqldb</groupId>
+ <artifactId>hsqldb</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Package as an executable jar -->
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/Department.java b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/Department.java
new file mode 100644
index 0000000..46e5292
--- /dev/null
+++ b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/Department.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.samples.db.beans;
+
+import java.util.List;
+
+import org.apache.empire.db.DBCommand;
+import org.apache.empire.db.DBContext;
+import org.apache.empire.db.list.DataBean;
+import org.apache.empire.samples.springboot.SampleDB;
+
+public class Department implements DataBean<SampleDB>
+{
+ private long id; // "ID"
+ private String name; // "FIRSTNAME"
+ private String head; // "FIRSTNAME"
+
+ private List<Employee> employees;
+
+ public long getId()
+ {
+ return id;
+ }
+ public void setId(long id)
+ {
+ this.id = id;
+ }
+ public String getName()
+ {
+ return name;
+ }
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+ public String getHead()
+ {
+ return head;
+ }
+ public void setHead(String head)
+ {
+ this.head = head;
+ }
+
+ public List<Employee> getEmployees()
+ {
+ return employees;
+ }
+
+ @Override
+ public void initialize(SampleDB db, DBContext context, int rownum, Object parent)
+ {
+ DBCommand cmd = context.createCommand();
+ cmd.where(db.EMPLOYEES.DEPARTMENT_ID.is(this.id));
+ cmd.orderBy(db.EMPLOYEES.FIRST_NAME, db.EMPLOYEES.LAST_NAME);
+ employees = context.getUtils().queryBeanList(cmd, Employee.class, db.EMPLOYEES, this);
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/Employee.java b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/Employee.java
new file mode 100644
index 0000000..3f41c49
--- /dev/null
+++ b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/Employee.java
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.samples.db.beans;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.empire.commons.DateUtils;
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.db.DBCommand;
+import org.apache.empire.db.DBContext;
+import org.apache.empire.db.list.DataBean;
+import org.apache.empire.samples.springboot.SampleDB;
+import org.apache.empire.samples.springboot.SampleDB.Gender;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is an employee entity bean
+ * @author doebele
+ *
+ */
+public class Employee implements DataBean<SampleDB>
+{
+ private static final Logger log = LoggerFactory.getLogger(Employee.class);
+
+ private long id; // "ID"
+ private String salutation; // "SALUTATION"
+ private String firstName; // "FIRST_NAME"
+ private String lastName; // "LAST_NAME"
+ private Date dateOfBirth; // "DATE_OF_BIRTH"
+ private long departmentId;// "DEPARTMENT_ID"
+ private Gender gender; // "GENDER"
+ private String phoneNumber; // "PHONE_NUMBER"
+ private String email; // "EMAIL"
+ private BigDecimal salary; // "SALARY"
+ private boolean retired; // "RETIRED"
+
+ private Department department;
+ private List<Payment> payments;
+
+ int rownum;
+
+ /**
+ * Constructor using all fields from the table EMPLOYEES
+ * @param id
+ * @param firstname
+ * @param lastname
+ * @param dateOfBirth
+ * @param departmentId
+ * @param gender
+ * @param phoneNumber
+ * @param salary
+ * @param retired
+ public Employee(long id, String salutation, String firstName, String lastName, Date dateOfBirth, long departmentId, Gender gender,
+ String phoneNumber, String email, BigDecimal salary, boolean retired, Timestamp timestamp)
+ {
+ this.id = id;
+ this.salutation = salutation;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.dateOfBirth = dateOfBirth;
+ this.departmentId = departmentId;
+ this.gender = gender;
+ this.phoneNumber = phoneNumber;
+ this.email = email;
+ this.salary = salary;
+ this.retired = retired;
+
+ log.info("Employee bean created using fields constructor");
+ }
+ */
+
+ /**
+ * Constructor using fields but without timestamp
+ */
+ public Employee(long id, String salutation, String firstName, String lastName, Date dateOfBirth, long departmentId, Gender gender,
+ String phoneNumber, String email, BigDecimal salary, boolean retired)
+ {
+ this.id = id;
+ this.salutation = salutation;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.dateOfBirth = dateOfBirth;
+ this.departmentId = departmentId;
+ this.gender = gender;
+ this.phoneNumber = phoneNumber;
+ this.email = email;
+ this.salary = salary;
+ this.retired = retired;
+
+ log.info("Employee bean created using fields constructor without timestamp");
+ }
+
+ /**
+ * Constructor using primary key fields
+ */
+ public Employee(long id)
+ {
+ this.id = id;
+ log.info("Employee bean created using primary key constructor");
+ }
+
+ /**
+ * Standard Constructor
+ */
+ public Employee()
+ {
+ // Standard constructor
+ log.info("Employee bean created using standard constructor");
+ }
+
+ public String getSalutation()
+ {
+ return salutation;
+ }
+
+ public void setSalutation(String salutation)
+ {
+ this.salutation = salutation;
+ }
+
+ public String getFirstName()
+ {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName)
+ {
+ this.firstName = firstName;
+ }
+
+ public String getLastName()
+ {
+ return lastName;
+ }
+
+ public void setLastName(String lastName)
+ {
+ this.lastName = lastName;
+ }
+
+ public Date getDateOfBirth()
+ {
+ return dateOfBirth;
+ }
+
+ public void setDateOfBirth(Date dateOfBirth)
+ {
+ this.dateOfBirth = dateOfBirth;
+ }
+
+ public long getDepartmentId()
+ {
+ return departmentId;
+ }
+
+ public void setDepartmentId(long departmentId)
+ {
+ this.departmentId = departmentId;
+ }
+
+ public Gender getGender()
+ {
+ return gender;
+ }
+
+ public void setGender(Gender gender)
+ {
+ this.gender = gender;
+ }
+
+ public String getPhoneNumber()
+ {
+ return phoneNumber;
+ }
+
+ public void setPhoneNumber(String phoneNumber)
+ {
+ this.phoneNumber = phoneNumber;
+ }
+
+ public String getEmail()
+ {
+ return email;
+ }
+
+ public void setEmail(String email)
+ {
+ this.email = email;
+ }
+
+ public BigDecimal getSalary()
+ {
+ return salary;
+ }
+
+ public void setSalary(BigDecimal salary)
+ {
+ this.salary = salary;
+ }
+
+ public boolean isRetired()
+ {
+ return retired;
+ }
+
+ public void setRetired(boolean retired)
+ {
+ this.retired = retired;
+ }
+
+ public long getId()
+ {
+ return id;
+ }
+
+ public int getRownum()
+ {
+ return rownum;
+ }
+
+ public Department getDepartment()
+ {
+ return department;
+ }
+
+ public List<Payment> getPayments()
+ {
+ return payments;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append(rownum);
+ buf.append("\t");
+ buf.append(id);
+ buf.append("\t");
+ buf.append(firstName);
+ buf.append("\t");
+ buf.append(lastName);
+ buf.append("\t");
+ buf.append(DateUtils.formatDate(dateOfBirth, Locale.US));
+ buf.append("\t");
+ buf.append(gender);
+ buf.append("\t");
+ buf.append(salary);
+ buf.append("\t");
+ buf.append(retired);
+ return buf.toString();
+ }
+
+ @Override
+ public void initialize(SampleDB db, DBContext context, int rownum, Object parent)
+ {
+ this.rownum = rownum;
+
+ if (parent instanceof Department)
+ department = ((Department)parent);
+ else
+ log.warn("Department Entity has not been provided.");
+ // Don't to that: (because of recursion)
+ // else department = context.getUtils().queryBean(Department.class, DBRecord.key(this.departmentId));
+
+ DBCommand cmd = context.createCommand();
+ cmd.where(db.PAYMENTS.EMPLOYEE_ID.is(this.id));
+ cmd.orderBy(db.PAYMENTS.YEAR.desc());
+ cmd.orderBy(db.PAYMENTS.MONTH.desc());
+ payments = context.getUtils().queryBeanList(cmd, Payment.class, this);
+ log.info("{} Payments have been loaded for Employee {}.", payments.size(), StringUtils.concat(" ", this.firstName, this.lastName));
+ }
+
+}
+
diff --git a/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/EmployeeQuery.java b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/EmployeeQuery.java
new file mode 100644
index 0000000..ad7fdd0
--- /dev/null
+++ b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/EmployeeQuery.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.samples.db.beans;
+
+import java.math.BigDecimal;
+
+import org.apache.empire.samples.springboot.SampleDB.Gender;
+
+/**
+ * The SampleBean class is used to demonstrate JavaBean support for SQL-Queries.
+ * The SampleBean is used in the SampleApp's queryRecords function.
+ */
+public class EmployeeQuery
+{
+ private int employeeId;
+ private String employeeName;
+ private Gender gender;
+ private String phoneNumber;
+ private BigDecimal salary;
+ private String departmentName;
+ private String businessUnit;
+ private BigDecimal paymentsLastYear;
+
+ /*
+ * Uncomment this if you want to use constructor instead of setters
+ * Number of arguments and data types must match query!
+ *
+ public EmployeeQuery(int employeeId, String employeeName, Gender gender, String phoneNumber, BigDecimal salary
+ , String departmentName, String businessUnit, BigDecimal paymentsLastYear)
+ {
+ this.employeeId = employeeId;
+ this.employeeName = employeeName;
+ this.gender = gender;
+ this.phoneNumber = phoneNumber;
+ this.salary = salary;
+ this.departmentName = departmentName;
+ this.businessUnit = businessUnit;
+ this.paymentsLastYear = paymentsLastYear;
+ }
+ */
+
+ public int getEmployeeId()
+ {
+ return employeeId;
+ }
+
+ public void setEmployeeId(int employeeId)
+ {
+ this.employeeId = employeeId;
+ }
+
+ public String getEmployeeName()
+ {
+ return employeeName;
+ }
+
+ public void setEmployeeName(String employeeName)
+ {
+ this.employeeName = employeeName;
+ }
+
+ public Gender getGender()
+ {
+ return gender;
+ }
+
+ public void setGender(Gender gender)
+ {
+ this.gender = gender;
+ }
+
+ public String getPhoneNumber()
+ {
+ return phoneNumber;
+ }
+
+ public void setPhoneNumber(String phoneNumber)
+ {
+ this.phoneNumber = phoneNumber;
+ }
+
+ public BigDecimal getSalary()
+ {
+ return salary;
+ }
+
+ public void setSalary(BigDecimal salary)
+ {
+ this.salary = salary;
+ }
+
+ public String getDepartmentName()
+ {
+ return departmentName;
+ }
+
+ public void setDepartmentName(String departmentName)
+ {
+ this.departmentName = departmentName;
+ }
+
+ public String getBusinessUnit()
+ {
+ return businessUnit;
+ }
+
+ public void setBusinessUnit(String businessUnit)
+ {
+ this.businessUnit = businessUnit;
+ }
+
+ public BigDecimal getPaymentsLastYear()
+ {
+ return paymentsLastYear;
+ }
+
+ public void setPaymentsLastYear(BigDecimal paymentsLastYear)
+ {
+ this.paymentsLastYear = paymentsLastYear;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append(employeeId);
+ buf.append("\t");
+ buf.append(employeeName);
+ buf.append("\t");
+ buf.append(gender);
+ buf.append("\t");
+ buf.append(departmentName);
+ buf.append("\t");
+ buf.append(businessUnit);
+ buf.append("\t");
+ buf.append(salary);
+ buf.append("\t");
+ buf.append(paymentsLastYear);
+ return buf.toString();
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/Payment.java b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/Payment.java
new file mode 100644
index 0000000..d1c0027
--- /dev/null
+++ b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/db/beans/Payment.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.samples.db.beans;
+
+import java.math.BigDecimal;
+
+import org.apache.empire.db.DBContext;
+import org.apache.empire.db.list.DataBean;
+import org.apache.empire.samples.springboot.SampleDB;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Payment implements DataBean<SampleDB>
+{
+ private static final Logger log = LoggerFactory.getLogger(Payment.class);
+
+ private long employeeId;
+ private BigDecimal year;
+ private BigDecimal month;
+ private BigDecimal amount;
+
+ private Employee employee;
+
+ public Payment(long employeeId, BigDecimal year, BigDecimal month, BigDecimal amount)
+ {
+ super();
+ this.employeeId = employeeId;
+ this.year = year;
+ this.month = month;
+ this.amount = amount;
+ }
+
+ public long getEmployeeId()
+ {
+ return employeeId;
+ }
+
+ public BigDecimal getYear()
+ {
+ return year;
+ }
+
+ public BigDecimal getMonth()
+ {
+ return month;
+ }
+
+ public BigDecimal getAmount()
+ {
+ return amount;
+ }
+
+ public Employee getEmployee()
+ {
+ return employee;
+ }
+
+ @Override
+ public void initialize(SampleDB db, DBContext context, int rownum, Object parent)
+ {
+ if (parent instanceof Employee)
+ this.employee = (Employee)parent;
+ else
+ log.warn("Employee Entity has not been provided.");
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/springboot/EmpireDBConfigProperties.java b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/springboot/EmpireDBConfigProperties.java
new file mode 100644
index 0000000..ab4d427
--- /dev/null
+++ b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/springboot/EmpireDBConfigProperties.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.samples.springboot;
+
+import java.util.Map;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "empiredb")
+public class EmpireDBConfigProperties {
+
+ private String dbmsHandlerClass;
+ private Map<String, String> dbmsHandlerProperties;
+
+ public String getDbmsHandlerClass() {
+ return dbmsHandlerClass;
+ }
+
+ public Map<String, String> getDbmsHandlerProperties() {
+ return dbmsHandlerProperties;
+ }
+
+ public void setDbmsHandlerClass(String dbmsHandlerClass) {
+ this.dbmsHandlerClass = dbmsHandlerClass;
+ }
+
+ public void setDbmsHandlerProperties(Map<String, String> dbmsHandlerProperties) {
+ this.dbmsHandlerProperties = dbmsHandlerProperties;
+ }
+}
diff --git a/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/springboot/SampleApp.java b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/springboot/SampleApp.java
new file mode 100644
index 0000000..92075d0
--- /dev/null
+++ b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/springboot/SampleApp.java
@@ -0,0 +1,708 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.samples.springboot;
+
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Map;
+import javax.sql.DataSource;
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.data.Record;
+import org.apache.empire.data.bean.BeanResult;
+import org.apache.empire.data.list.DataListEntry;
+import org.apache.empire.db.DBColumnExpr;
+import org.apache.empire.db.DBCommand;
+import org.apache.empire.db.DBContext;
+import org.apache.empire.db.DBQuery;
+import org.apache.empire.db.DBReader;
+import org.apache.empire.db.DBRecord;
+import org.apache.empire.db.DBRecordBean;
+import org.apache.empire.db.DBRowSet;
+import org.apache.empire.db.context.DBContextStatic;
+import org.apache.empire.db.generic.TRecord;
+import org.apache.empire.dbms.DBMSHandler;
+import org.apache.empire.dbms.postgresql.DBMSHandlerPostgreSQL;
+import org.apache.empire.exceptions.InvalidArgumentException;
+import org.apache.empire.samples.db.beans.Department;
+import org.apache.empire.samples.db.beans.Employee;
+import org.apache.empire.samples.db.beans.EmployeeQuery;
+import org.apache.empire.samples.db.beans.Payment;
+import org.apache.empire.samples.springboot.SampleDB.Gender;
+import org.apache.empire.xml.XMLWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.core.env.Environment;
+import org.w3c.dom.Document;
+
+/**
+ * Implementing ApplicationRunner interface tells Spring Boot to automatically call the run method AFTER the application context has been loaded.
+ */
+@SpringBootApplication
+public class SampleApp implements ApplicationRunner {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SampleApp.class);
+
+ /**
+ * <PRE>
+ * This is the entry point of the Empire-DB Spring Boot Sample Application
+ * Please check the application.yml configuration file for Database and Connection settings.
+ *
+ * See run() method below for what is executed.
+ * </PRE>
+ *
+ * @param args arguments
+ */
+ public static void main(String[] args) {
+ SpringApplication.run(SampleApp.class, args);
+ }
+
+ private DBContext context = null;
+
+ @Autowired
+ private DataSource dataSource;
+
+ @Autowired
+ private SampleDB db;
+
+ @Autowired
+ private EmpireDBConfigProperties empireDBConfigProperties;
+
+ @Autowired
+ private Environment environment;
+
+ /**
+ * <PRE>
+ * Empties all Tables.
+ * </PRE>
+ */
+ private void clearDatabase() {
+ DBCommand cmd = context.createCommand();
+ // Delete all Payments (no constraints)
+ context.executeDelete(db.PAYMENTS, cmd);
+ // Delete all Employees (no constraints)
+ context.executeDelete(db.EMPLOYEES, cmd);
+ // Delete all Departments (no constraints)
+ context.executeDelete(db.DEPARTMENTS, cmd);
+ // commit
+ context.commit();
+ }
+
+ /**
+ * Creates an Empire-db DBMSHandler for the DBMS configured in application.yml and applies driver specific configuration
+ */
+ private DBMSHandler getDBMSHandler(Connection conn) {
+ try {
+ // Get DBMSHandler Class Name
+ String dbmsHandlerClass = empireDBConfigProperties.getDbmsHandlerClass();
+ if (StringUtils.isEmpty(dbmsHandlerClass)) {
+ throw new RuntimeException("Configuration error: Element 'empiredb.dbmsHandlerClass' not found in properties of profile '" + environment.getActiveProfiles().toString() + "'");
+ }
+
+ // Create dbms
+ DBMSHandler dbms = (DBMSHandler) Class.forName(dbmsHandlerClass).newInstance();
+
+ // Configure dbms
+ readProperties(dbms);
+
+ // Special cases
+ if (dbms instanceof DBMSHandlerPostgreSQL) {
+ // Create the reverse function that is needed by this sample
+ ((DBMSHandlerPostgreSQL) dbms).createReverseFunction(conn);
+ }
+
+ // done
+ return dbms;
+ } catch (Exception e) {
+ // catch any checked exception and forward it
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * <PRE>
+ * Opens and returns a JDBC-Connection.
+ * JDBC url, user and password for the connection are obtained from the DataSource bean
+ * Please use the application.yml file to change connection params.
+ * </PRE>
+ */
+ private Connection getJDBCConnection() {
+ // Establish a new database connection
+ Connection conn = null;
+ try {
+ conn = dataSource.getConnection();
+ LOGGER.info("Connected successfully");
+ // set the AutoCommit to false for this connection.
+ // commit must be called explicitly!
+ conn.setAutoCommit(false);
+ LOGGER.info("AutoCommit has been set to " + conn.getAutoCommit());
+ } catch (SQLException e) {
+// LOGGER.error("Failed to connect directly to '" + config.getJdbcURL() + "' / User=" + config.getJdbcUser());
+ LOGGER.error(e.toString());
+ throw new RuntimeException(e);
+ }
+ return conn;
+ }
+
+ /**
+ * <PRE>
+ * Insert a Department into the Departments table.
+ * </PRE>
+ */
+ private long insertDepartment(String departmentName, String businessUnit) {
+ SampleDB.Departments DEP = db.DEPARTMENTS;
+ // Insert a Department
+ TRecord<SampleDB.Departments> rec = new TRecord<SampleDB.Departments>(context, DEP);
+ rec.create()
+ .set(DEP.NAME, departmentName)
+ .set(DEP.BUSINESS_UNIT, businessUnit)
+ .update();
+ // Return Department ID
+ return rec.getIdentity();
+ }
+
+ /**
+ * <PRE>
+ * Inserts an Employee into the Employees table.
+ * </PRE>
+ */
+ private long insertEmployee(long departmentId, String firstName, String lastName, Gender gender, int salary) {
+ SampleDB.Employees EMP = db.EMPLOYEES;
+ // Insert an Employee
+ DBRecord rec = new DBRecord(context, EMP);
+ rec.create(null)
+ .set(EMP.DEPARTMENT_ID, departmentId)
+ .set(EMP.FIRST_NAME, firstName)
+ .set(EMP.LAST_NAME, lastName)
+ .set(EMP.GENDER, gender)
+ .set(EMP.SALARY, salary)
+ .update();
+ // insert payments
+ if (salary > 0) {
+ insertPayments(rec);
+ }
+ // Return Employee ID
+ return rec.getIdentity();
+ }
+
+ /**
+ * <PRE>
+ * Inserts an Payments for a particular Employee
+ * </PRE>
+ */
+ private void insertPayments(DBRecord employee) {
+ if (employee.isNull(db.EMPLOYEES.SALARY)) {
+ return; // No salary
+ } // monthlySalary
+ BigDecimal monthlySalary = employee.getDecimal(db.EMPLOYEES.SALARY).divide(new BigDecimal(12), 2, RoundingMode.HALF_UP);
+ // Insert an Employee
+ LocalDate date = LocalDate.now();
+ date = date.minusDays(date.getDayOfMonth() - 1); // first day of this month
+ // Add Payment for each month
+ SampleDB.Payments PAY = db.PAYMENTS;
+ DBRecord rec = new DBRecord(context, PAY);
+ int months = (int) (Math.random() * 6) + 17;
+ for (LocalDate month = date.minusMonths(months); !month.isAfter(date); month = month.plusMonths(1)) {
+ BigDecimal variation = new BigDecimal((Math.random() * 200) - 100.0);
+ variation = variation.setScale(2, RoundingMode.HALF_UP);
+ // insert
+ rec.create(DBRecord.key(employee.getIdentity(), month.getYear(), month.getMonth()));
+ rec.set(PAY.AMOUNT, monthlySalary.add(variation));
+ rec.update();
+ }
+ }
+
+ private void populateAndModify() {
+ clearDatabase();
+
+ LOGGER.info("Step 5: insertDepartment() & insertEmployee()");
+ long idDevDep = insertDepartment("Development", "ITTK");
+ long idSalDep = insertDepartment("Sales", "ITTK");
+ // Insert Employees
+ long idEmp1 = insertEmployee(idDevDep, "Peter", "Sharp", Gender.M, 25000);
+ long idEmp2 = insertEmployee(idDevDep, "Fred", "Bloggs", Gender.M, 0);
+ long idEmp3 = insertEmployee(idSalDep, "Emma", "White", Gender.F, 19500);
+ long idEmp4 = insertEmployee(idSalDep, "John", "Doe", Gender.M, 18800);
+ long idEmp5 = insertEmployee(idDevDep, "Sarah", "Smith", Gender.F, 44000);
+
+ // commit
+ context.commit();
+
+ // SECTION 6: Modify some data
+ LOGGER.info("Step 6: updateEmployee()");
+ updateEmployee(idEmp1, "+49-7531-457160");
+ updateEmployee(idEmp2, "+49-5555-505050");
+ // Partial Record
+ updatePartialRecord(idEmp3, "+49-040-125486");
+ // Update Joined Records (Make Fred Bloggs head of department and set salary)
+ updateJoinedRecords(idEmp2, 100000);
+ }
+
+ private void queryBeans() {
+ SampleDB.Employees EMP = db.EMPLOYEES;
+
+ DBCommand cmd = context.createCommand();
+ cmd.where(EMP.GENDER.is(Gender.M));
+ cmd.orderBy(EMP.LAST_NAME.desc());
+ List<Employee> list = context.getUtils().queryBeanList(cmd, Employee.class, null);
+ for (Employee emp : list) {
+ System.out.println(emp.toString());
+ }
+
+ // load department
+ Department department = context.getUtils().queryBean(Department.class, db.DEPARTMENTS.NAME.is("Sales"));
+ Payment first = department.getEmployees().get(0).getPayments().get(0);
+ LOGGER.info("First payment amount is {}", first.getAmount());
+
+ // Query all males
+ BeanResult<Employee> result = new BeanResult<Employee>(Employee.class, EMP);
+ result.getCommand().where(EMP.GENDER.is(Gender.M));
+ result.fetch(context);
+
+ LOGGER.info("Number of male employees is: " + result.size());
+
+ // And now, the females
+ result.getCommand().where(EMP.GENDER.is(Gender.F));
+ result.fetch(context);
+
+ LOGGER.info("Number of female employees is: " + result.size());
+ }
+
+ private void queryDataList() {
+ int lastYear = LocalDate.now().getYear() - 1;
+
+ // Define shortcuts for tables used - not necessary but convenient
+ SampleDB.Employees EMP = db.EMPLOYEES;
+ SampleDB.Departments DEP = db.DEPARTMENTS;
+ SampleDB.Payments PAY = db.PAYMENTS;
+
+ // Employee total query
+ DBColumnExpr EMP_TOTAL = PAY.AMOUNT.sum().as("EMP_TOTAL");
+ DBCommand cmdEmpTotal = context.createCommand()
+ .select(PAY.EMPLOYEE_ID, EMP_TOTAL)
+ .where(PAY.YEAR.is(lastYear))
+ .groupBy(PAY.EMPLOYEE_ID);
+ DBQuery Q_EMP_TOTAL = new DBQuery(cmdEmpTotal, "qet");
+
+ // Department total query
+ DBColumnExpr DEP_TOTAL = PAY.AMOUNT.sum().as("DEP_TOTAL");
+ DBCommand cmdDepTotal = context.createCommand()
+ .select(EMP.DEPARTMENT_ID, DEP_TOTAL)
+ .join(PAY.EMPLOYEE_ID, EMP.ID)
+ .where(PAY.YEAR.is(lastYear))
+ .groupBy(EMP.DEPARTMENT_ID);
+ DBQuery Q_DEP_TOTAL = new DBQuery(cmdDepTotal, "qdt");
+
+ // Percentage of department
+ DBColumnExpr PCT_OF_DEP_COST = Q_EMP_TOTAL.column(EMP_TOTAL).multiplyWith(100).divideBy(Q_DEP_TOTAL.column(DEP_TOTAL));
+ // Create the employee query
+ DBCommand cmd = context.createCommand()
+ .select(EMP.ID, EMP.FIRST_NAME, EMP.LAST_NAME, DEP.NAME.as("DEPARTMENT"))
+ .select(Q_EMP_TOTAL.column(EMP_TOTAL))
+ .select(PCT_OF_DEP_COST.as("PCT_OF_DEPARTMENT_COST"))
+ // join Employee with Department
+ .join(EMP.DEPARTMENT_ID, DEP.ID)
+ // Join with Subqueries
+ .joinLeft(EMP.ID, Q_EMP_TOTAL.column(PAY.EMPLOYEE_ID))
+ .joinLeft(DEP.ID, Q_DEP_TOTAL.column(EMP.DEPARTMENT_ID))
+ // Order by
+ .orderBy(DEP.NAME.desc())
+ .orderBy(EMP.LAST_NAME);
+
+ List<DataListEntry> list = context.getUtils().queryDataList(cmd);
+ /* uncomment this to print full list
+ for (DataListEntry dle : list)
+ System.out.println(dle.toString());
+ */
+ for (DataListEntry dle : list) {
+ long empId = dle.getRecordId(EMP);
+ // int depId = dle.getId(DEP);
+ String empName = StringUtils.concat(", ", dle.getString(EMP.LAST_NAME), dle.getString(EMP.FIRST_NAME));
+ String depName = dle.getString(DEP.NAME);
+ boolean hasPayments = !dle.isNull(Q_EMP_TOTAL.column(EMP_TOTAL));
+ if (hasPayments) { // report
+ BigDecimal empTotal = dle.getDecimal(Q_EMP_TOTAL.column(EMP_TOTAL));
+ BigDecimal pctOfDep = dle.getDecimal(PCT_OF_DEP_COST).setScale(1, RoundingMode.HALF_UP);
+ LOGGER.info("Eployee[{}]: {}\tDepartment: {}\tPayments: {} ({}% of Department)", empId, empName, depName, empTotal, pctOfDep);
+ } else {
+ LOGGER.info("Eployee[{}]: {}\tDepartment: {}\tPayments: [No data avaiable]", empId, empName, depName);
+ }
+ }
+
+ /*
+ cmd.where(EMP.ID.is(list.get(0).getRecordId(EMP)));
+ DataListEntry emp1 = context.getUtils().queryDataEntry(cmd);
+ System.out.println(emp1.toString());
+
+ cmd.where(EMP.ID.is(list.get(1).getRecordId(EMP)));
+ DataListEntry emp2 = context.getUtils().queryDataEntry(cmd);
+ System.out.println(emp2.toString());
+ */
+ }
+
+ /**
+ * <PRE>
+ * Performs an SQL-Query and prints the result to System.out
+ *
+ * First a DBCommand object is used to create the following SQL-Query (Oracle-Syntax):
+ *
+ * SELECT t2.EMPLOYEE_ID, t2.LASTNAME || ', ' || t2.FIRSTNAME AS FULL_NAME, t2.GENDER, t2.PHONE_NUMBER,
+ * substr(t2.PHONE_NUMBER, length(t2.PHONE_NUMBER)-instr(reverse(t2.PHONE_NUMBER), '-')+2) AS PHONE_EXTENSION,
+ * t1.NAME AS DEPARTMENT, t1.BUSINESS_UNIT
+ * FROM EMPLOYEES t2 INNER JOIN DEPARTMENTS t1 ON t1.DEPARTMENT_ID = t2.ID
+ * WHERE length(t2.LASTNAME)>0
+ * ORDER BY t2.LASTNAME, t2.FIRSTNAME
+ *
+ * For processing the rows there are three options available:
+ *
+ * QueryType.Reader:
+ * Iterates through all rows and prints field values as tabbed text.
+ *
+ * QueryType.BeanList:
+ * Obtains the query result as a list of JavaBean objects of type SampleBean.
+ * It then iterates through the list of beans and uses bean.toString() for printing.
+ *
+ * QueryType.XmlDocument:
+ * Obtains the query result as an XML-Document and prints the document.
+ * Please note, that the XML not only contains the data but also the field metadata.
+ * </PRE>
+ */
+ private void queryExample(QueryType queryType) {
+ int lastYear = LocalDate.now().getYear() - 1;
+
+ // Define shortcuts for tables used - not necessary but convenient
+ SampleDB.Employees EMP = db.EMPLOYEES;
+ SampleDB.Departments DEP = db.DEPARTMENTS;
+ SampleDB.Payments PAY = db.PAYMENTS;
+
+ // The following expression concats lastname + ', ' + firstname
+ DBColumnExpr EMPLOYEE_NAME = EMP.LAST_NAME.append(", ").append(EMP.FIRST_NAME).as("EMPLOYEE_NAME");
+ DBColumnExpr PAYMENTS_LAST_YEAR = PAY.AMOUNT.sum().as("PAYMENTS_LAST_YEAR");
+
+ /*
+ // Example: Extracts the extension number from the phone field
+ // e.g. substr(PHONE_NUMBER, length(PHONE_NUMBER)-instr(reverse(PHONE_NUMBER), '-')+2) AS PHONE_EXTENSION
+ // Hint: Since the reverse() function is not supported by HSQLDB there is special treatment for HSQL
+ DBColumnExpr PHONE_LAST_DASH;
+ if ( db.getDbms() instanceof DBMSHandlerHSql
+ || db.getDbms() instanceof DBMSHandlerDerby
+ || db.getDbms() instanceof DBMSHandlerH2)
+ PHONE_LAST_DASH = EMP.PHONE_NUMBER.indexOf("-", EMP.PHONE_NUMBER.indexOf("-").plus(1)).plus(1); // HSQLDB only
+ else PHONE_LAST_DASH = EMP.PHONE_NUMBER.length().minus(EMP.PHONE_NUMBER.reverse().indexOf("-")).plus(2);
+ DBColumnExpr PHONE_EXT_NUMBER = EMP.PHONE_NUMBER.substring(PHONE_LAST_DASH).as("PHONE_EXTENSION");
+ */
+
+ /*
+ // Example: Select the Gender-Enum as String
+ // e.g. case t2.GENDER when 'U' then 'Unknown' when 'M' then 'Male' when 'F' then 'Female' end
+ DBColumnExpr GENDER_NAME = EMP.GENDER.decode(EMP.GENDER.getOptions()).as("GENDER_NAME");
+ */
+ // Select Employee and Department columns
+ DBCommand cmd = context.createCommand()
+ .selectQualified(EMP.ID) // select "EMPLOYEE_ID"
+ .select(EMPLOYEE_NAME, EMP.GENDER, EMP.PHONE_NUMBER, EMP.SALARY)
+ .selectQualified(DEP.NAME) // "DEPARMENT_NAME"
+ .select(DEP.BUSINESS_UNIT) // "BUSINESS_UNIT"
+ // Joins
+ .join(EMP.DEPARTMENT_ID, DEP.ID)
+ .joinLeft(EMP.ID, PAY.EMPLOYEE_ID, PAY.YEAR.is(lastYear))
+ // Where constraints
+ .where(EMP.LAST_NAME.length().isGreaterThan(0)) // always true, just for show
+ .where(EMP.GENDER.in(Gender.M, Gender.F)) // always true, just for show
+ .where(EMP.RETIRED.is(false)) // always true, just for show
+ // Order by
+ .orderBy(EMPLOYEE_NAME);
+
+ // Add payment of last year using a SUM aggregation
+ cmd.groupBy(cmd.getSelectExpressions());
+ cmd.select(PAYMENTS_LAST_YEAR);
+
+ /*
+ * Example for limitRows() and skipRows()
+ * Uncomment if you wish
+ *
+ if (db.getDbms().isSupported(DBMSFeature.QUERY_LIMIT_ROWS))
+ { // set maximum number of rows
+ cmd.limitRows(20);
+ if (db.getDbms().isSupported(DBMSFeature.QUERY_SKIP_ROWS))
+ cmd.skipRows(1);
+ }
+ */
+ // Query Records and print output
+ DBReader reader = new DBReader(context);
+ try {
+ // log select statement (but only once)
+ if (queryType == QueryType.Reader) {
+ LOGGER.info("Running Query: {}", cmd.getSelect());
+ }
+ // Open Reader
+ reader.open(cmd);
+ // Print output
+ System.out.println("---------------------------------");
+ switch (queryType) {
+ case Reader:
+ // Text-Output by iterating through all records.
+ while (reader.moveNext()) {
+ System.out.println(reader.getText(EMP.ID)
+ + "\t" + reader.getText(EMPLOYEE_NAME)
+ + "\t" + reader.getText(EMP.GENDER)
+ + "\t" + reader.getText(EMP.SALARY)
+ + "\t" + reader.getText(PAYMENTS_LAST_YEAR)
+ + "\t" + reader.getText(DEP.NAME));
+ }
+ break;
+ case BeanList:
+ // Text-Output using a list of Java Beans supplied by the DBReader
+ List<EmployeeQuery> beanList = reader.getBeanList(EmployeeQuery.class);
+ // log.info(String.valueOf(beanList.size()) + " SampleBeans returned from Query.");
+ for (EmployeeQuery b : beanList) {
+ System.out.println(b.toString());
+ }
+ break;
+ case XmlDocument:
+ // XML Output
+ Document doc = reader.getXmlDocument();
+ // Print XML Document to System.out
+ XMLWriter.debug(doc);
+ break;
+ }
+ System.out.println("---------------------------------");
+ } finally {
+ // Always close Reader!
+ reader.close();
+ }
+ }
+
+ private void queryRecordList() {
+ SampleDB.Departments DEP = db.DEPARTMENTS;
+ SampleDB.Employees EMP = db.EMPLOYEES;
+ /*
+ * Test RecordList
+ */
+ DBCommand cmd = context.createCommand();
+ cmd.join(EMP.DEPARTMENT_ID, DEP.ID);
+ cmd.where(DEP.NAME.is("Development"));
+ // query now
+ List<DBRecordBean> list = context.getUtils().queryRecordList(cmd, EMP, DBRecordBean.class);
+ LOGGER.info("RecordList query found {} employees in Development department", list.size());
+ for (DBRecordBean record : list) {
+ Object[] key = record.getKey();
+ // print info
+ String empName = StringUtils.concat(", ", record.getString(EMP.LAST_NAME), record.getString(EMP.FIRST_NAME));
+ String phone = record.getString(EMP.PHONE_NUMBER);
+ BigDecimal salary = record.getDecimal(EMP.SALARY);
+ LOGGER.info("Eployee[{}]: {}\tPhone: {}\tSalary: {}", StringUtils.toString(key), empName, phone, salary);
+ // modify salary
+ BigDecimal newSalary = new BigDecimal(2000 + ((Math.random() * 200) - 100.0));
+ record.set(EMP.SALARY, newSalary);
+ // check
+ if (record.wasModified(EMP.SALARY)) { // Salary was modified
+ LOGGER.info("Salary was modified for {}. New salary is {}", empName, record.getDecimal(EMP.SALARY));
+ }
+ // udpate the record
+ record.update(context);
+
+ // convert to bean
+ Employee employee = new Employee();
+ record.setBeanProperties(employee);
+ System.out.println(employee.toString());
+ }
+ }
+
+ /**
+ * reads all properties for given profile and applies them to the given bean
+ *
+ * @param bean the bean to which to apply the configuration
+ */
+ private void readProperties(Object bean) {
+ // Check arguments
+ if (bean == null) {
+ throw new InvalidArgumentException("bean", bean);
+ }
+
+ Map<String, String> dbmsHandlerProperties = empireDBConfigProperties.getDbmsHandlerProperties();
+ if (dbmsHandlerProperties != null) {
+ for (Map.Entry<String, String> entry : dbmsHandlerProperties.entrySet()) {
+ String name = entry.getKey();
+ String newValue = entry.getValue();
+ try {
+ BeanUtils.setProperty(bean, name, newValue);
+
+ Object value = BeanUtils.getProperty(bean, name);
+ if (ObjectUtils.compareEqual(newValue, value)) {
+ LOGGER.info("Configuration property '{}' set to \"{}\"", name, newValue);
+ } else {
+ LOGGER.error("Failed to set property '{}'. Value is \"{}\"", name, value);
+ }
+ } catch (IllegalAccessException ex) {
+ LOGGER.error(null, ex);
+ } catch (InvocationTargetException ex) {
+ LOGGER.error(null, ex);
+ } catch (NoSuchMethodException ex) {
+ LOGGER.error("Property '{}' not found in {}", name, bean.getClass().getName());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ LOGGER.info("STARTING THE APPLICATION");
+
+ LOGGER.info("Running DB Sample...");
+
+ // SECTION 1: Get a JDBC Connection
+ LOGGER.info("*** Step 1: getJDBCConnection() ***");
+ Connection conn = getJDBCConnection();
+
+ // SECTION 2: Choose a DBMSHandler
+ LOGGER.info("*** Step 2: getDBMSHandler() ***");
+ DBMSHandler dbms = getDBMSHandler(conn);
+
+ // SECTION 3: Create a Context
+ LOGGER.info("*** Step 3: create context ***");
+ context = new DBContextStatic(dbms, conn, true)
+ // set optional context features
+ .setPreparedStatementsEnabled(false)
+ .setRollbackHandlingEnabled(false);
+
+ // SECTION 4: Open Database
+ LOGGER.info("*** Step 4: Open database (and create if not existing) ***");
+ db.open(context);
+
+ // SECTION 5 AND 6: Populate Database and modify Data
+ DBCommand cmd = context.createCommand();
+ cmd.select(db.EMPLOYEES.count());
+ if (context.getUtils().querySingleInt(cmd) == 0) { // Employess table is empty. Populate now
+ populateAndModify();
+ }
+
+ // SECTION 7: Option 1: Query Records and print tab-separated
+ LOGGER.info("Step 8 Option 1: queryRecords() / Tab-Output");
+ queryExample(QueryType.Reader); // Tab-Output
+
+ // SECTION 7: Option 2: Query Records as a list of java beans
+ LOGGER.info("Step 8 Option 2: queryRecords() / Bean-List-Output");
+ queryExample(QueryType.BeanList); // Bean-List-Output
+
+ // SECTION 7: Option 3: Query Records as XML
+ LOGGER.info("Step 8 Option 3: queryRecords() / XML-Output");
+ queryExample(QueryType.XmlDocument); // XML-Output
+
+ // SECTION 8: Use DataList query
+ queryDataList();
+
+ // SECTION 9: Use RecordList query
+ queryRecordList();
+
+ // SECTION 10: Use Bean Result to query beans
+ queryBeans();
+
+
+ /*
+ int idEmp = testTransactionCreate(idDevDep);
+ testTransactionUpdate(idEmp);
+ testTransactionDelete(idEmp);
+ */
+ // Finally, commit any changes
+ context.commit();
+ }
+
+ /**
+ * <PRE>
+ * Updates an employee record by setting the phone number.
+ * </PRE>
+ */
+ private void updateEmployee(long idEmp, String phoneNumber) {
+ /*
+ // Update an Employee
+ DBRecord rec = new DBRecord(context, db.EMPLOYEES);
+ rec.read(idEmp);
+ // Set
+ rec.set(db.EMPLOYEES.PHONE_NUMBER, phoneNumber);
+ rec.update();
+ */
+
+ DBRecordBean rec = new DBRecordBean();
+ rec.read(context, db.EMPLOYEES, idEmp);
+ // Set
+ rec.set(db.EMPLOYEES.PHONE_NUMBER, phoneNumber);
+ rec.update(context);
+ }
+
+ /**
+ * <PRE>
+ * Updates an employee record by setting the phone number.
+ * </PRE>
+ */
+ private void updateJoinedRecords(long idEmp, int salary) {
+ // Shortcuts for convenience
+ SampleDB.Employees EMP = db.EMPLOYEES;
+ SampleDB.Departments DEP = db.DEPARTMENTS;
+
+ // Create DBQuery from command
+ DBCommand cmd = context.createCommand();
+ cmd.select(EMP.getColumns());
+ cmd.select(DEP.getColumns());
+ cmd.join(EMP.DEPARTMENT_ID, DEP.ID);
+ DBQuery query = new DBQuery(cmd, EMP.ID);
+
+ // Make employee Head of Department and update salary
+ DBRecord rec = new DBRecord(context, query);
+ rec.read(idEmp);
+ rec.set(EMP.SALARY, salary);
+ rec.set(DEP.HEAD, rec.getString(EMP.LAST_NAME));
+ rec.update();
+ }
+
+ /**
+ * <PRE>
+ * Updates an employee record by setting the phone number.
+ * </PRE>
+ */
+ private void updatePartialRecord(long employeeId, String phoneNumber) {
+ // Shortcut for convenience
+ SampleDB.Employees EMP = db.EMPLOYEES;
+ // Update an Employee with partial record
+ // this will only load the EMPLOYEE ID and the PHONE_NUMBER
+ DBRecord rec = new DBRecord(context, EMP);
+ rec.read(Record.key(employeeId), DBRowSet.PartialMode.INCLUDE, EMP.SALUTATION, EMP.FIRST_NAME, EMP.LAST_NAME, EMP.PHONE_NUMBER, EMP.EMAIL);
+ // Set
+ rec.set(db.EMPLOYEES.PHONE_NUMBER, phoneNumber);
+ rec.update();
+ }
+
+ private enum QueryType {
+ Reader,
+ BeanList,
+ XmlDocument
+ }
+}
diff --git a/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/springboot/SampleDB.java b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/springboot/SampleDB.java
new file mode 100644
index 0000000..bcd7dfe
--- /dev/null
+++ b/empire-db-examples/empire-db-example-spring-boot/src/main/java/org/apache/empire/samples/springboot/SampleDB.java
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.samples.springboot;
+
+import java.sql.SQLException;
+
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.DBColumn;
+import org.apache.empire.db.DBContext;
+import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.DBSQLScript;
+import org.apache.empire.db.DBTable;
+import org.apache.empire.db.DBTableColumn;
+import org.apache.empire.db.validation.DBModelChecker;
+import org.apache.empire.db.validation.DBModelErrorLogger;
+import org.apache.empire.dbms.DBMSHandler;
+import org.apache.empire.dbms.postgresql.DBMSHandlerPostgreSQL;
+import org.apache.empire.samples.db.beans.Department;
+import org.apache.empire.samples.db.beans.Employee;
+import org.apache.empire.samples.db.beans.Payment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * <PRE>
+ * This file contains the definition of the data model in Java.
+ * The SampleDB data model consists of three tables and two foreign key relations.
+ * The tables are defined as nested classes here, but you may put them in separate files if you want to.
+ *
+ * The class also provided methods to create the database (if it doesn't exist)
+ * and to check the datamodel i.e. to compare this definition to the database in the DBMS
+ *
+ * PLEASE NOTE THE NAMING CONVENTION:
+ * Since all tables, views and columns are declared as "final" constants we have all put them in upper case.
+ *
+ * You may declare other database tables or views in the same way.
+ * </PRE>
+ */
+@Component
+public class SampleDB extends DBDatabase
+{
+ // Logger
+ private static final Logger log = LoggerFactory.getLogger(SampleDB.class);
+
+ /**
+ * Gender enum
+ */
+ public enum Gender
+ {
+ M("Male"),
+ F("Female"),
+ U("Unknown");
+
+ private final String title;
+ private Gender(String title)
+ {
+ this.title = title;
+ }
+ @Override
+ public String toString()
+ {
+ return title;
+ }
+ }
+
+ /**
+ * This class represents the Departments table.
+ */
+ public static class Departments extends DBTable
+ {
+ public final DBTableColumn ID;
+ public final DBTableColumn NAME;
+ public final DBTableColumn HEAD;
+ public final DBTableColumn BUSINESS_UNIT;
+ public final DBTableColumn UPDATE_TIMESTAMP;
+
+ public Departments(SampleDB db)
+ {
+ super("DEPARTMENTS", db);
+ // ID
+ ID = addIdentity ("ID", "DEP_ID_SEQUENCE"); // Optional Sequence for some DBMS (e.g. Oracle)
+ NAME = addColumn ("NAME", DataType.VARCHAR, 80, true);
+ HEAD = addColumn ("HEAD", DataType.VARCHAR, 80, false);
+ BUSINESS_UNIT = addColumn ("BUSINESS_UNIT", DataType.VARCHAR, 4, true, "ITTK");
+ UPDATE_TIMESTAMP= addTimestamp("UPDATE_TIMESTAMP");
+
+ // Primary Key (automatically set due to addIdentity()) otherwise use
+ // setPrimaryKey(...);
+
+ // Set other Indexes
+ addIndex("DEPARTMENT_NAME_IDX", true, new DBColumn[] { NAME });
+
+ // Set beanType (optional)
+ setBeanType(Department.class);
+ }
+ }
+
+ /**
+ * This class represents the Employees table.
+ */
+ public static class Employees extends DBTable
+ {
+ public final DBTableColumn ID;
+ public final DBTableColumn SALUTATION;
+ public final DBTableColumn FIRST_NAME;
+ public final DBTableColumn LAST_NAME;
+ public final DBTableColumn DATE_OF_BIRTH;
+ public final DBTableColumn DEPARTMENT_ID;
+ public final DBTableColumn GENDER;
+ public final DBTableColumn PHONE_NUMBER;
+ public final DBTableColumn EMAIL;
+ public final DBTableColumn SALARY;
+ public final DBTableColumn RETIRED;
+ public final DBTableColumn UPDATE_TIMESTAMP;
+
+ public Employees(SampleDB db)
+ {
+ super("EMPLOYEES", db);
+
+ // ID
+ ID = addIdentity ("ID", "EMPLOYEE_ID_SEQUENCE"); // Optional Sequence name for some DBMS (e.g. Oracle)
+ SALUTATION = addColumn ("SALUTATION", DataType.VARCHAR, 5, false);
+ FIRST_NAME = addColumn ("FIRST_NAME", DataType.VARCHAR, 40, true);
+ LAST_NAME = addColumn ("LAST_NAME", DataType.VARCHAR, 40, true);
+ DATE_OF_BIRTH = addColumn ("DATE_OF_BIRTH", DataType.DATE, 0, false);
+ DEPARTMENT_ID = addForeignKey("DEPARTMENT_ID", db.DEPARTMENTS, true);
+ GENDER = addColumn ("GENDER", DataType.VARCHAR, 1, true, Gender.class);
+ PHONE_NUMBER = addColumn ("PHONE_NUMBER", DataType.VARCHAR, 40, false);
+ EMAIL = addColumn ("EMAIL", DataType.VARCHAR, 80, false);
+ SALARY = addColumn ("SALARY", DataType.DECIMAL, 10.2, false);
+ RETIRED = addColumn ("RETIRED", DataType.BOOL, 0, true, false);
+ UPDATE_TIMESTAMP= addTimestamp ("UPDATE_TIMESTAMP");
+
+ // Primary Key (automatically set due to addIdentity()) otherwise use
+ // setPrimaryKey(...);
+
+ // Set other Indexes
+ addIndex("EMPLOYEE_NAME_IDX", true, new DBColumn[] { FIRST_NAME, LAST_NAME, DATE_OF_BIRTH });
+
+ // Set beanType (optional)
+ setBeanType(Employee.class);
+ }
+ }
+
+ /**
+ * This class represents the Payments table.
+ */
+ public static class Payments extends DBTable
+ {
+ public final DBTableColumn EMPLOYEE_ID;
+ public final DBTableColumn YEAR;
+ public final DBTableColumn MONTH;
+ public final DBTableColumn AMOUNT;
+
+ public Payments(SampleDB db)
+ {
+ super("PAYMENTS", db);
+
+ // ID
+ EMPLOYEE_ID = addForeignKey("EMPLOYEE_ID", db.EMPLOYEES, true);
+ YEAR = addColumn("YEAR", DataType.DECIMAL, 4.0, true);
+ MONTH = addColumn("MONTH", DataType.DECIMAL, 2.0, true);
+ AMOUNT = addColumn("AMOUNT", DataType.DECIMAL, 8.2, true);
+
+ // Primary Key
+ setPrimaryKey(EMPLOYEE_ID, YEAR, MONTH);
+
+ // Set beanType (optional)
+ setBeanType(Payment.class);
+ }
+ }
+
+ // Declare all Tables and Views here
+ public final Departments DEPARTMENTS = new Departments(this);
+ public final Employees EMPLOYEES = new Employees(this);
+ public final Payments PAYMENTS = new Payments(this);
+
+ /**
+ * Constructor of the SampleDB data model
+ *
+ * Put all foreign key relations here.
+ */
+ public SampleDB()
+ {
+ // Define additional Foreign-Key Relations here
+ // which have not already been defined by addForeignKey()
+ // addRelation( {Source Column}.referenceOn( {Target Column} ));
+ log.info("SampleDB has been created with {} Tables and {} Relations", getTables().size(), getRelations().size());
+ }
+
+ @Override
+ public void open(DBContext context)
+ {
+ // Enable prepared statements
+ // setPreparedStatementsEnabled(true);
+ // Check exists
+ if (checkExists(context))
+ { // attach to driver
+ super.open(context);
+ // yes, it exists, then check the model
+ checkDataModel(context);
+ }
+ else
+ { // PostgreSQL does not support DDL in transaction
+ DBMSHandler dbms = context.getDbms();
+ if (dbms instanceof DBMSHandlerPostgreSQL)
+ setAutoCommit(context, true);
+ // create the database
+ createDatabase(context);
+ // PostgreSQL does not support DDL in transaction
+ if (dbms instanceof DBMSHandlerPostgreSQL)
+ setAutoCommit(context, false);
+ // attach to driver
+ super.open(context);
+ }
+ }
+
+ private void createDatabase(DBContext context)
+ {
+ // create DDL for Database Definition
+ DBSQLScript script = new DBSQLScript(context);
+ getCreateDDLScript(script);
+ // Show DDL Statement
+ log.info(script.toString());
+ // Execute Script
+ script.executeAll(false);
+ // Commit
+ context.commit();
+ }
+
+ private void checkDataModel(DBContext context)
+ { try {
+ DBModelChecker modelChecker = context.getDbms().createModelChecker(this);
+ // Check data model
+ log.info("Checking DataModel for {} using {}", getClass().getSimpleName(), modelChecker.getClass().getSimpleName());
+ // dbo schema
+ DBModelErrorLogger logger = new DBModelErrorLogger();
+ modelChecker.checkModel(this, context.getConnection(), logger);
+ // show result
+ log.info("Data model check done. Found {} errors and {} warnings.", logger.getErrorCount(), logger.getWarnCount());
+ } catch(Exception e) {
+ log.error("FATAL error when checking data model. Probably not properly implemented by DBMSHandler!");
+ }
+ }
+
+ private void setAutoCommit(DBContext context, boolean enable)
+ { try {
+ context.getConnection().setAutoCommit(enable);
+ } catch (SQLException e) {
+ log.error("Unable to set AutoCommit on Connection", e);
+ }
+ }
+}
diff --git a/empire-db-examples/empire-db-example-spring-boot/src/main/resources/application.yml b/empire-db-examples/empire-db-example-spring-boot/src/main/resources/application.yml
new file mode 100644
index 0000000..0f944d4
--- /dev/null
+++ b/empire-db-examples/empire-db-example-spring-boot/src/main/resources/application.yml
@@ -0,0 +1,111 @@
+spring:
+ profiles:
+ active: hsqldb
+ datasource:
+ # Required jar file: hsqldb.jar
+ url: 'jdbc:hsqldb:file:hsqldb/sample;shutdown=true'
+ username: 'sa'
+ password: ''
+
+empiredb:
+ dbmsHandlerClass: 'org.apache.empire.dbms.hsql.DBMSHandlerHSql'
+
+---
+
+spring:
+ config:
+ activate:
+ on-profile: mysql
+ datasource:
+ # Required jar file: mysql-connector-java-5.1.6-bin.jar
+ url: 'jdbc:mysql://localhost'
+ username: 'root'
+ password: 'esteam'
+
+empiredb:
+ dbmsHandlerClass: 'org.apache.empire.dbms.mysql.DBMSHandlerMySQL'
+ dbmsHandlerProperties:
+ databaseName: 'DBSAMPLE'
+
+---
+
+spring:
+ config:
+ activate:
+ on-profile: postgresql
+ datasource:
+ # Required jar file: postgresql-8.3-603.jdbc4.jar
+ url: 'jdbc:postgresql://localhost/dbsample'
+ username: 'dbsample'
+ password: 'DBSAMPLE'
+
+empiredb:
+ dbmsHandlerClass: 'org.apache.empire.dbms.postgresql.DBMSHandlerPostgreSQL'
+
+---
+
+spring:
+ config:
+ activate:
+ on-profile: h2
+ datasource:
+ # Required jar file: h2-1.1.107.jar
+ url: 'jdbc:h2:h2/DBSAMPLE'
+ username: 'sa'
+ password: ''
+
+empiredb:
+ dbmsHandlerClass: 'org.apache.empire.dbms.h2.DBMSHandlerH2'
+ dbmsHandlerProperties:
+ databaseName: 'DBSAMPLE'
+
+---
+
+spring:
+ config:
+ activate:
+ on-profile: derby
+ datasource:
+ # Required jar file: derby-10.4.2.0.jar
+ url: 'jdbc:derby:derby/DBSAMPLE;create=true'
+ username: ''
+ password: ''
+
+empiredb:
+ dbmsHandlerClass: 'org.apache.empire.dbms.derby.DBMSHandlerDerby'
+ dbmsHandlerProperties:
+ databaseName: 'DBSAMPLE'
+
+---
+
+spring:
+ config:
+ activate:
+ on-profile: oracle
+ datasource:
+ # Required jar file: ojdbc14.jar
+ url: 'jdbc:oracle:thin:@esteams12:1521:ora12'
+ username: 'DBSAMPLE'
+ password: 'DBSAMPLE'
+
+empiredb:
+ dbmsHandlerClass: 'org.apache.empire.dbms.oracle.DBMSHandlerOracle'
+
+---
+
+spring:
+ config:
+ activate:
+ on-profile: sqlserver
+ datasource:
+ url: 'jdbc:sqlserver://esteams12\sql17'
+ username: 'jdbcUser'
+ password: 'jdbcPwd'
+
+empiredb:
+ dbmsHandlerClass: 'org.apache.empire.dbms.sqlserver.DBMSHandlerMSSQL'
+ dbmsHandlerProperties:
+ databaseName: 'DBSAMPLE'
+ # useDateTime2: set to false for databases using DATETIME for timestamps instead of DATETIME2
+ useDateTime2: true
+ useSequenceTable: false
\ No newline at end of file