From 4275d5d3d3d491e57037baa32bc0401ae9f9bfa4 Mon Sep 17 00:00:00 2001 From: Arnaud Roger Date: Mon, 2 Jan 2017 14:17:13 +0000 Subject: [PATCH 1/2] add jdbc template with sfm --- build.gradle | 4 +- .../api/data/RegisterEmployeeInput.java | 47 +++ .../SfmJDBCDataRepositoryImpl.java | 271 ++++++++++++++++++ .../dbtests/SfmJdbcTemplateScenariosTest.java | 91 ++++++ 4 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/clevergang/dbtests/repository/api/data/RegisterEmployeeInput.java create mode 100644 src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/SfmJDBCDataRepositoryImpl.java create mode 100644 src/test/java/com/clevergang/dbtests/SfmJdbcTemplateScenariosTest.java diff --git a/build.gradle b/build.gradle index a2bbcb3..0477e2c 100644 --- a/build.gradle +++ b/build.gradle @@ -40,7 +40,9 @@ dependencies { 'com.github.javaplugs:mybatis-types:0.3', // required so mybatis works with java8 datetime classes (LocalDate) // jdbi dependencies - 'org.jdbi:jdbi:2.77' + 'org.jdbi:jdbi:2.77', + + 'org.simpleflatmapper:sfm-springjdbc:3.6' ) testCompile( diff --git a/src/main/java/com/clevergang/dbtests/repository/api/data/RegisterEmployeeInput.java b/src/main/java/com/clevergang/dbtests/repository/api/data/RegisterEmployeeInput.java new file mode 100644 index 0000000..a216020 --- /dev/null +++ b/src/main/java/com/clevergang/dbtests/repository/api/data/RegisterEmployeeInput.java @@ -0,0 +1,47 @@ +package com.clevergang.dbtests.repository.api.data; + +import java.math.BigDecimal; + +public class RegisterEmployeeInput { + + private final String name; + private final String surname; + private final String email; + private final BigDecimal salary; + private final String departmentName; + private final String companyName; + + + public RegisterEmployeeInput(String name, String surname, String email, BigDecimal salary, String departmentName, String companyName) { + this.name = name; + this.surname = surname; + this.email = email; + this.salary = salary; + this.departmentName = departmentName; + this.companyName = companyName; + } + + public String getName() { + return name; + } + + public String getSurname() { + return surname; + } + + public String getEmail() { + return email; + } + + public BigDecimal getSalary() { + return salary; + } + + public String getDepartmentName() { + return departmentName; + } + + public String getCompanyName() { + return companyName; + } +} diff --git a/src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/SfmJDBCDataRepositoryImpl.java b/src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/SfmJDBCDataRepositoryImpl.java new file mode 100644 index 0000000..5af5590 --- /dev/null +++ b/src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/SfmJDBCDataRepositoryImpl.java @@ -0,0 +1,271 @@ +package com.clevergang.dbtests.repository.impl.jdbctemplate; + +import com.clevergang.dbtests.repository.api.DataRepository; +import com.clevergang.dbtests.repository.api.data.Company; +import com.clevergang.dbtests.repository.api.data.Department; +import com.clevergang.dbtests.repository.api.data.Employee; +import com.clevergang.dbtests.repository.api.data.Project; +import com.clevergang.dbtests.repository.api.data.ProjectsWithCostsGreaterThanOutput; +import com.clevergang.dbtests.repository.api.data.RegisterEmployeeInput; +import com.clevergang.dbtests.repository.api.data.RegisterEmployeeOutput; +import org.simpleflatmapper.jdbc.spring.JdbcTemplateCrud; +import org.simpleflatmapper.jdbc.spring.JdbcTemplateMapperFactory; +import org.simpleflatmapper.jdbc.spring.SqlParameterSourceFactory; +import org.simpleflatmapper.map.property.RenameProperty; +import org.simpleflatmapper.util.CheckedConsumer; +import org.simpleflatmapper.util.ListCollector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; + +import java.math.BigDecimal; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Spring JDBCTemplate with simpleflatmapper implementation of the DataRepository + * + */ +@Repository +public class SfmJDBCDataRepositoryImpl implements DataRepository { + private static final Logger logger = LoggerFactory.getLogger(SfmJDBCDataRepositoryImpl.class); + + private final NamedParameterJdbcTemplate jdbcTemplate; + + private final JdbcTemplateCrud companyCrud; + private final JdbcTemplateCrud projectCrud; + private final JdbcTemplateCrud employeeCrud; + private final JdbcTemplateCrud departmentCrud; + + private final RowMapper companyMapper; + private final RowMapper departmentMapper; + private final RowMapper employeeMapper; + private final RowMapper projectsWithCostsGreaterThanOutputRowMapper; + private final RowMapper registerEmployeeOutputRowMapper; + + private final SqlParameterSourceFactory registerEmployeeInputSqlParameterSourceFactory; + + @SuppressWarnings("SpringJavaAutowiringInspection") + @Autowired + public SfmJDBCDataRepositoryImpl(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + + JdbcOperations jdbcOperations = jdbcTemplate.getJdbcOperations(); + + JdbcTemplateMapperFactory mapperFactory = JdbcTemplateMapperFactory + .newInstance(); + + + companyCrud = + mapperFactory + .crud(Company.class, Integer.class) + .to(jdbcOperations, "company"); + employeeCrud = + mapperFactory + .crud(Employee.class, Integer.class) + .to(jdbcOperations, "employee"); + + + departmentCrud = + mapperFactory + .crud(Department.class, Integer.class) + .to(jdbcOperations, "department"); + + projectCrud = + JdbcTemplateMapperFactory + .newInstance() + .addAlias("datestarted", "date") + .crud(Project.class, Integer.class) + .to(jdbcOperations, "project"); + + companyMapper = + mapperFactory.newRowMapper(Company.class); + + departmentMapper = mapperFactory.newRowMapper(Department.class); + employeeMapper = mapperFactory.newRowMapper(Employee.class); + projectsWithCostsGreaterThanOutputRowMapper = mapperFactory.newRowMapper(ProjectsWithCostsGreaterThanOutput.class); + registerEmployeeOutputRowMapper = JdbcTemplateMapperFactory + .newInstance() + .addColumnProperty( + k -> k.getName().contains("id"), + k -> new RenameProperty(k.getName().replace("id", "pid"))) + .newRowMapper(RegisterEmployeeOutput.class); + + registerEmployeeInputSqlParameterSourceFactory = mapperFactory.newSqlParameterSourceFactory(RegisterEmployeeInput.class); + } + + @Override + public Company findCompany(Integer pid) { + logger.info("Finding Company by ID using companyCrud"); + + Company company = companyCrud.read(pid); + + logger.info("Found company: " + company); + + return company; + } + + @Override + public Company findCompanyUsingSimpleStaticStatement(Integer pid) { + String query; + query = "SELECT pid, address, name " + + "FROM company " + + "WHERE pid = " + pid; + + + Company company = jdbcTemplate.getJdbcOperations().queryForObject(query, companyMapper); + + logger.info("Found company: " + company); + + return company; + } + + @Override + public void removeProject(Integer pid) { + projectCrud.delete(pid); + } + + @Override + public Department findDepartment(Integer pid) { + return departmentCrud.read(pid); + } + + @Override + public List employeesWithSalaryGreaterThan(Integer minSalary) { + logger.info("Looking for employeesWithSalaryGreaterThan using JDBCTemplate"); + + String query = "SELECT * " + + " FROM employee" + + " WHERE salary > :salary"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("salary", minSalary); + + // using BeanPropertyRowMapper is easier, but with much worse performance than custom RowMapper + return jdbcTemplate.query(query, params, employeeMapper); + } + + @Override + public Integer insertProject(Project project) { + return projectCrud.create(project, new CheckedConsumer() { + Integer key; + @Override + public void accept(Integer integer) throws Exception { + this.key = integer; + } + }).key; + } + + @Override + public List insertProjects(List projects) { + return projectCrud.create(projects, new ListCollector()).getList(); + } + + @Override + public void updateEmployee(Employee employeeToUpdate) { + logger.info("Updating employee using JDBC Template"); + employeeCrud.update(employeeToUpdate); + } + + @Override + public List getProjectsWithCostsGreaterThan(int totalCostBoundary) { + String query; + query = "WITH project_info AS (\n" + + " SELECT project.pid project_pid, project.name project_name, salary monthly_cost, company.name company_name\n" + + " FROM project\n" + + " JOIN projectemployee ON project.pid = projectemployee.project_pid\n" + + " JOIN employee ON projectemployee.employee_pid = employee.pid\n" + + " LEFT JOIN department ON employee.department_pid = department.pid\n" + + " LEFT JOIN company ON department.company_pid = company.pid\n" + + "),\n" + + "project_cost AS (\n" + + " SELECT project_pid, sum(monthly_cost) total_cost\n" + + " FROM project_info GROUP BY project_pid\n" + + ")\n" + + "SELECT project_name, total_cost, company_name, sum(monthly_cost) company_cost FROM project_info\n" + + " JOIN project_cost USING (project_pid)\n" + + "WHERE total_cost > :totalCostBoundary\n" + + "GROUP BY project_name, total_cost, company_name\n" + + "ORDER BY company_name"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("totalCostBoundary", totalCostBoundary); + + return jdbcTemplate.query(query, params, projectsWithCostsGreaterThanOutputRowMapper); + } + + @Override + public Employee findEmployee(Integer pid) { + return employeeCrud.read(pid); + } + + @Override + public RegisterEmployeeOutput callRegisterEmployee(String name, String surname, String email, BigDecimal salary, String departmentName, String companyName) { + String query; + //noinspection SqlResolve + query = "SELECT employee_id, department_id, company_id FROM register_employee(\n" + + " _name := :name, \n" + + " _surname := :surname, \n" + + " _email := :email, \n" + + " _salary := :salary, \n" + + " _department_name := :departmentName, \n" + + " _company_name := :companyName\n" + + ")"; + + SqlParameterSource params = + registerEmployeeInputSqlParameterSourceFactory + .newSqlParameterSource( + new RegisterEmployeeInput(name, surname, email, salary, departmentName, companyName)); + + return jdbcTemplate.queryForObject(query, params, registerEmployeeOutputRowMapper); + } + + @Override + public Integer getProjectsCount() { + String query = "SELECT count(*) FROM project"; + return jdbcTemplate.getJdbcOperations().queryForObject(query, Integer.class); + } + + @Override + public List findDepartmentsOfCompany(Company company) { + String query = "SELECT pid, company_pid, name" + + " FROM department " + + " WHERE company_pid = :pid" + + " ORDER BY pid"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("pid", company.getPid()); + + return jdbcTemplate.query(query, params, departmentMapper); + } + + @Override + public void deleteDepartments(List departmentsToDelete) { + List keys = departmentsToDelete.stream() + .map(Department::getPid) + .collect(Collectors.toList()); + + departmentCrud.delete(keys); + } + + @Override + public void updateDepartments(List departmentsToUpdate) { + departmentCrud.update(departmentsToUpdate); + } + + @Override + public void insertDepartments(List departmentsToInsert) { + departmentCrud.create(departmentsToInsert); + } + + @Override + public Project findProject(Integer pid) { + return projectCrud.read(pid); + } +} diff --git a/src/test/java/com/clevergang/dbtests/SfmJdbcTemplateScenariosTest.java b/src/test/java/com/clevergang/dbtests/SfmJdbcTemplateScenariosTest.java new file mode 100644 index 0000000..4381251 --- /dev/null +++ b/src/test/java/com/clevergang/dbtests/SfmJdbcTemplateScenariosTest.java @@ -0,0 +1,91 @@ +package com.clevergang.dbtests; + +import com.clevergang.dbtests.repository.impl.jdbctemplate.SfmJDBCDataRepositoryImpl; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest +@ContextConfiguration(classes = DbTestsApplication.class) +@Transactional +@Rollback +public class SfmJdbcTemplateScenariosTest { + + @Autowired + private SfmJDBCDataRepositoryImpl jdbcRepository; + + private Scenarios scenarios; + + @Before + public void setup() { + scenarios = new Scenarios(jdbcRepository); + } + + @Test + public void scenarioOne() { + scenarios.fetchSingleEntityScenario(1); + } + + @Test + public void scenarioTwo() { + scenarios.fetchListOfEntitiesScenario(25000); + } + + @Test + public void scenarioThree() { + scenarios.saveNewEntityScenario(); + } + + @Test + public void scenarioFour() { + scenarios.batchInsertMultipleEntitiesScenario(); + } + + @Test + public void scenarioFive() { + scenarios.updateCompleteEntityScenario(); + } + + @Test + public void scenarioSix() { + scenarios.fetchManyToOneRelationScenario(); + } + + @Test + public void scenarioSeven() { + scenarios.fetchOneToManyRelationScenario(); + } + + @Test + public void scenarioEight() { + scenarios.updateCompleteOneToManyRelationScenario(); + } + + @Test + public void scenarioNine() { + scenarios.executeComplexSelectScenario(); + } + + @Test + public void scenarioTen() { + scenarios.callStoredProcedureScenario(); + } + + @Test + public void scenarioEleven() { + scenarios.executeSimpleStaticStatementScenario(); + } + + @Test + public void scenarioTwelve() { + scenarios.removeSingleEntityScenario(); + } + +} From e92dc4cc0ce0960b220d3d271c7c27299ee15cad Mon Sep 17 00:00:00 2001 From: Arnaud Roger Date: Mon, 2 Jan 2017 22:07:52 +0000 Subject: [PATCH 2/2] clean up name --- build.gradle | 1 + ...ava => JDBCWithSfmDataRepositoryImpl.java} | 58 +++++++++++-------- ... => JdbcWithSfmTemplateScenariosTest.java} | 6 +- 3 files changed, 37 insertions(+), 28 deletions(-) rename src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/{SfmJDBCDataRepositoryImpl.java => JDBCWithSfmDataRepositoryImpl.java} (82%) rename src/test/java/com/clevergang/dbtests/{SfmJdbcTemplateScenariosTest.java => JdbcWithSfmTemplateScenariosTest.java} (91%) diff --git a/build.gradle b/build.gradle index 0477e2c..01405af 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,7 @@ dependencies { // jdbi dependencies 'org.jdbi:jdbi:2.77', + // simpleflatmapper 'org.simpleflatmapper:sfm-springjdbc:3.6' ) diff --git a/src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/SfmJDBCDataRepositoryImpl.java b/src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/JDBCWithSfmDataRepositoryImpl.java similarity index 82% rename from src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/SfmJDBCDataRepositoryImpl.java rename to src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/JDBCWithSfmDataRepositoryImpl.java index 5af5590..44e166d 100644 --- a/src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/SfmJDBCDataRepositoryImpl.java +++ b/src/main/java/com/clevergang/dbtests/repository/impl/jdbctemplate/JDBCWithSfmDataRepositoryImpl.java @@ -33,34 +33,36 @@ * */ @Repository -public class SfmJDBCDataRepositoryImpl implements DataRepository { - private static final Logger logger = LoggerFactory.getLogger(SfmJDBCDataRepositoryImpl.class); +public class JDBCWithSfmDataRepositoryImpl implements DataRepository { + private static final Logger logger = LoggerFactory.getLogger(JDBCWithSfmDataRepositoryImpl.class); private final NamedParameterJdbcTemplate jdbcTemplate; - private final JdbcTemplateCrud companyCrud; - private final JdbcTemplateCrud projectCrud; - private final JdbcTemplateCrud employeeCrud; + // Cruds provide insert, update, select and delete. + private final JdbcTemplateCrud companyCrud; + private final JdbcTemplateCrud projectCrud; + private final JdbcTemplateCrud employeeCrud; private final JdbcTemplateCrud departmentCrud; - private final RowMapper companyMapper; + // Spring row mappers implementation + private final RowMapper companyMapper; private final RowMapper departmentMapper; - private final RowMapper employeeMapper; + private final RowMapper employeeMapper; + private final RowMapper projectsWithCostsGreaterThanOutputRowMapper; - private final RowMapper registerEmployeeOutputRowMapper; + private final RowMapper registerEmployeeOutputRowMapper; + // Spring SqlParameterSource factory to create parameter source from RegisterEmployeeInput private final SqlParameterSourceFactory registerEmployeeInputSqlParameterSourceFactory; @SuppressWarnings("SpringJavaAutowiringInspection") @Autowired - public SfmJDBCDataRepositoryImpl(NamedParameterJdbcTemplate jdbcTemplate) { + public JDBCWithSfmDataRepositoryImpl(NamedParameterJdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; JdbcOperations jdbcOperations = jdbcTemplate.getJdbcOperations(); - JdbcTemplateMapperFactory mapperFactory = JdbcTemplateMapperFactory - .newInstance(); - + JdbcTemplateMapperFactory mapperFactory = JdbcTemplateMapperFactory.newInstance(); companyCrud = mapperFactory @@ -71,15 +73,15 @@ public SfmJDBCDataRepositoryImpl(NamedParameterJdbcTemplate jdbcTemplate) { .crud(Employee.class, Integer.class) .to(jdbcOperations, "employee"); - departmentCrud = mapperFactory .crud(Department.class, Integer.class) - .to(jdbcOperations, "department"); + .to(jdbcOperations); // if table name is not specified will look for a matching one projectCrud = JdbcTemplateMapperFactory .newInstance() + // add alias from column datastarted to date property .addAlias("datestarted", "date") .crud(Project.class, Integer.class) .to(jdbcOperations, "project"); @@ -87,17 +89,23 @@ public SfmJDBCDataRepositoryImpl(NamedParameterJdbcTemplate jdbcTemplate) { companyMapper = mapperFactory.newRowMapper(Company.class); - departmentMapper = mapperFactory.newRowMapper(Department.class); - employeeMapper = mapperFactory.newRowMapper(Employee.class); - projectsWithCostsGreaterThanOutputRowMapper = mapperFactory.newRowMapper(ProjectsWithCostsGreaterThanOutput.class); - registerEmployeeOutputRowMapper = JdbcTemplateMapperFactory - .newInstance() - .addColumnProperty( - k -> k.getName().contains("id"), - k -> new RenameProperty(k.getName().replace("id", "pid"))) - .newRowMapper(RegisterEmployeeOutput.class); - - registerEmployeeInputSqlParameterSourceFactory = mapperFactory.newSqlParameterSourceFactory(RegisterEmployeeInput.class); + departmentMapper = + mapperFactory.newRowMapper(Department.class); + employeeMapper = + mapperFactory.newRowMapper(Employee.class); + projectsWithCostsGreaterThanOutputRowMapper = + mapperFactory.newRowMapper(ProjectsWithCostsGreaterThanOutput.class); + registerEmployeeOutputRowMapper = + JdbcTemplateMapperFactory + .newInstance() + // add rules to rename id column name to pid in output object + .addColumnProperty( + k -> k.getName().contains("id"), + k -> new RenameProperty(k.getName().replace("id", "pid"))) + .newRowMapper(RegisterEmployeeOutput.class); + + registerEmployeeInputSqlParameterSourceFactory = + mapperFactory.newSqlParameterSourceFactory(RegisterEmployeeInput.class); } @Override diff --git a/src/test/java/com/clevergang/dbtests/SfmJdbcTemplateScenariosTest.java b/src/test/java/com/clevergang/dbtests/JdbcWithSfmTemplateScenariosTest.java similarity index 91% rename from src/test/java/com/clevergang/dbtests/SfmJdbcTemplateScenariosTest.java rename to src/test/java/com/clevergang/dbtests/JdbcWithSfmTemplateScenariosTest.java index 4381251..99b140e 100644 --- a/src/test/java/com/clevergang/dbtests/SfmJdbcTemplateScenariosTest.java +++ b/src/test/java/com/clevergang/dbtests/JdbcWithSfmTemplateScenariosTest.java @@ -1,6 +1,6 @@ package com.clevergang.dbtests; -import com.clevergang.dbtests.repository.impl.jdbctemplate.SfmJDBCDataRepositoryImpl; +import com.clevergang.dbtests.repository.impl.jdbctemplate.JDBCWithSfmDataRepositoryImpl; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -16,10 +16,10 @@ @ContextConfiguration(classes = DbTestsApplication.class) @Transactional @Rollback -public class SfmJdbcTemplateScenariosTest { +public class JdbcWithSfmTemplateScenariosTest { @Autowired - private SfmJDBCDataRepositoryImpl jdbcRepository; + private JDBCWithSfmDataRepositoryImpl jdbcRepository; private Scenarios scenarios;