├── .gitignore ├── src ├── main │ ├── docs │ │ ├── classes.png │ │ └── yuml.txt │ └── java │ │ └── com │ │ └── nurkiewicz │ │ └── jdbcrepository │ │ ├── RowUnmapper.java │ │ ├── MissingRowUnmapper.java │ │ ├── sql │ │ ├── PostgreSqlGenerator.java │ │ ├── AbstractMssqlSqlGenerator.java │ │ ├── MssqlSqlGenerator.java │ │ ├── OracleSqlGenerator.java │ │ ├── SQL99Helper.java │ │ ├── DerbySqlGenerator.java │ │ ├── Mssql2012SqlGenerator.java │ │ └── SqlGenerator.java │ │ ├── TableDescription.java │ │ └── JdbcRepository.java └── test │ ├── java │ └── com │ │ └── nurkiewicz │ │ └── jdbcrepository │ │ ├── mssql │ │ ├── CommentWithUserMssqlGenerator.java │ │ ├── JdbcRepositoryManyToOneMssqlTest.java │ │ ├── JdbcRepositoryCompoundPkMssqlTest.java │ │ ├── JdbcRepositoryManualKeyMssqlTest.java │ │ ├── JdbcRepositoryGeneratedKeyMssqlTest.java │ │ └── JdbcRepositoryTestMssqlConfig.java │ │ ├── derby │ │ ├── CommentWithUserDerbySqlGenerator.java │ │ ├── JdbcRepositoryManyToOneDerbyTest.java │ │ ├── JdbcRepositoryCompoundPkDerbyTest.java │ │ ├── JdbcRepositoryGeneratedKeyDerbyTest.java │ │ ├── JdbcRepositoryManualKeyDerbyTest.java │ │ └── JdbcRepositoryTestDerbyConfig.java │ │ ├── h2 │ │ ├── JdbcRepositoryManualKeyH2Test.java │ │ ├── JdbcRepositoryManyToOneH2Test.java │ │ ├── JdbcRepositoryCompoundPkH2Test.java │ │ ├── JdbcRepositoryGeneratedKeyH2Test.java │ │ └── JdbcRepositoryTestH2Config.java │ │ ├── hsqldb │ │ ├── JdbcRepositoryManualKeyHsqldbTest.java │ │ ├── JdbcRepositoryManyToOneHsqldbTest.java │ │ ├── JdbcRepositoryCompoundPkHsqldbTest.java │ │ ├── JdbcRepositoryGeneratedKeyHsqldbTest.java │ │ └── JdbcRepositoryTestHsqldbConfig.java │ │ ├── oracle │ │ ├── JdbcRepositoryManyToOneOracleTest.java │ │ ├── JdbcRepositoryCompoundPkOracleTest.java │ │ ├── JdbcRepositoryManualKeyOracleTest.java │ │ ├── JdbcRepositoryGeneratedKeyOracleTest.java │ │ └── JdbcRepositoryTestOracleConfig.java │ │ ├── mssql2012 │ │ ├── JdbcRepositoryManyToOneMssql2012Test.java │ │ ├── JdbcRepositoryCompoundPkMssql2012Test.java │ │ ├── JdbcRepositoryManualKeyMssql2012Test.java │ │ ├── JdbcRepositoryGeneratedKeyMssql2012Test.java │ │ └── JdbcRepositoryTestMssql2012Config.java │ │ ├── mysql │ │ ├── JdbcRepositoryManyToOneMysqlTest.java │ │ ├── JdbcRepositoryCompoundPkMysqlTest.java │ │ ├── JdbcRepositoryManualKeyMysqlTest.java │ │ ├── JdbcRepositoryGeneratedKeyMysqlTest.java │ │ └── JdbcRepositoryTestMysqlConfig.java │ │ ├── postgresql │ │ ├── JdbcRepositoryManyToOnePostgresqlTest.java │ │ ├── JdbcRepositoryCompoundPkPostgresqlTest.java │ │ ├── JdbcRepositoryManualKeyPostgresqlTest.java │ │ ├── JdbcRepositoryGeneratedKeyPostgresqlTest.java │ │ └── JdbcRepositoryTestPostgresqlConfig.java │ │ ├── AbstractIntegrationTest.java │ │ ├── JdbcRepositoryTestConfig.java │ │ ├── repositories │ │ ├── CommentWithUser.java │ │ ├── UserRepository.java │ │ ├── BoardingPassRepository.java │ │ ├── CommentRepository.java │ │ ├── CommentWithUserRepository.java │ │ ├── User.java │ │ ├── BoardingPass.java │ │ └── Comment.java │ │ ├── StandaloneUsageTest.java │ │ ├── JdbcRepositoryGeneratedKeyTest.java │ │ ├── sql │ │ └── SqlGeneratorTest.java │ │ ├── JdbcRepositoryManyToOneTest.java │ │ ├── JdbcRepositoryCompoundPkTest.java │ │ └── JdbcRepositoryManualKeyTest.java │ └── resources │ ├── logback-test.xml │ ├── schema_mysql.sql │ ├── schema_hsqldb.sql │ ├── schema_derby.sql │ ├── schema_h2.sql │ ├── schema_postgresql.sql │ ├── schema_mssql.sql │ └── schema_oracle.sql ├── .travis.yml ├── checkstyle.xml ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.iml 3 | .idea 4 | derby.log -------------------------------------------------------------------------------- /src/main/docs/classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nurkiewicz/spring-data-jdbc-repository/HEAD/src/main/docs/classes.png -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/RowUnmapper.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import java.util.Map; 4 | 5 | public interface RowUnmapper { 6 | Map mapColumns(T t); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql/CommentWithUserMssqlGenerator.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql; 2 | 3 | import com.nurkiewicz.jdbcrepository.sql.MssqlSqlGenerator; 4 | 5 | /** 6 | * Author: tom 7 | */ 8 | public class CommentWithUserMssqlGenerator extends MssqlSqlGenerator { 9 | public CommentWithUserMssqlGenerator() { 10 | super("c.*, u.date_of_birth, u.reputation, u.enabled"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | install: mvn install -DskipTests=true -Dgpg.skip=true 3 | jdk: 4 | - oraclejdk7 5 | before_script: 6 | - mysql -e 'create database spring_data_jdbc_repository_test;' 7 | - mysql spring_data_jdbc_repository_test < src/test/resources/schema_mysql.sql 8 | - psql -c 'create database spring_data_jdbc_repository_test;' -U postgres 9 | - psql spring_data_jdbc_repository_test < src/test/resources/schema_postgresql.sql 10 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | %d{HH:mm:ss:SSS} | %-5level | %thread | %logger{20} | %msg%n%rEx 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/MissingRowUnmapper.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * @author Tomasz Nurkiewicz 7 | * @since 12/19/12, 9:45 PM 8 | */ 9 | public class MissingRowUnmapper implements RowUnmapper { 10 | @Override 11 | public Map mapColumns(Object o) { 12 | throw new UnsupportedOperationException("This repository is read-only, it can't store or update entities"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/derby/CommentWithUserDerbySqlGenerator.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.derby; 2 | 3 | import com.nurkiewicz.jdbcrepository.sql.DerbySqlGenerator; 4 | 5 | /** 6 | * @author Tomasz Nurkiewicz 7 | * @since 1/19/13, 10:13 PM 8 | */ 9 | public class CommentWithUserDerbySqlGenerator extends DerbySqlGenerator { 10 | 11 | public CommentWithUserDerbySqlGenerator() { 12 | super("c.*, u.date_of_birth, u.reputation, u.enabled"); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/sql/PostgreSqlGenerator.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.sql; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | 5 | /** 6 | * @author Tomasz Nurkiewicz 7 | * @since 1/15/13, 11:03 PM 8 | */ 9 | public class PostgreSqlGenerator extends SqlGenerator { 10 | @Override 11 | protected String limitClause(Pageable page) { 12 | final int offset = page.getPageNumber() * page.getPageSize(); 13 | return " LIMIT " + page.getPageSize() + " OFFSET " + offset; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/docs/yuml.txt: -------------------------------------------------------------------------------- 1 | [note: Important classes and abstractions{bg:cornsilk}] 2 | [<>>;JdbcRepository]^-[FooRepository] 3 | [<>>;JdbcRepository]^-[BarRepository] 4 | [BarRepository]++->[RowMapper] 5 | [BarRepository]++->[RowUnmapper] 6 | [BarRepository]->[SqlGenerator] 7 | [FooRepository]++->[RowMapper] 8 | [FooRepository]++->[SqlGenerator] 9 | [FooRepository]->[RowUnmapper] 10 | [SqlGenerator]^-[PostgreSqlGenerator] 11 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/sql/AbstractMssqlSqlGenerator.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.sql; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | 5 | /** 6 | * Author: tom 7 | */ 8 | abstract class AbstractMssqlSqlGenerator extends SqlGenerator { 9 | public AbstractMssqlSqlGenerator() { 10 | } 11 | 12 | public AbstractMssqlSqlGenerator(String allColumnsClause) { 13 | super(allColumnsClause); 14 | } 15 | 16 | @Override 17 | protected String limitClause(Pageable page) { 18 | return ""; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/h2/JdbcRepositoryManualKeyH2Test.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.h2; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManualKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:19 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestH2Config.class) 12 | public class JdbcRepositoryManualKeyH2Test extends JdbcRepositoryManualKeyTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/h2/JdbcRepositoryManyToOneH2Test.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.h2; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManyToOneTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestH2Config.class) 12 | public class JdbcRepositoryManyToOneH2Test extends JdbcRepositoryManyToOneTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/h2/JdbcRepositoryCompoundPkH2Test.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.h2; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryCompoundPkTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestH2Config.class) 12 | public class JdbcRepositoryCompoundPkH2Test extends JdbcRepositoryCompoundPkTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/schema_mysql.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE USERS ( 2 | user_name varchar(255), 3 | date_of_birth TIMESTAMP NOT NULL, 4 | reputation INT NOT NULL, 5 | enabled BIT(1) NOT NULL, 6 | PRIMARY KEY (user_name) 7 | ); 8 | 9 | CREATE TABLE COMMENTS ( 10 | id INT AUTO_INCREMENT, 11 | user_name varchar(256), 12 | contents varchar(1000), 13 | created_time TIMESTAMP NOT NULL, 14 | favourite_count INT NOT NULL, 15 | PRIMARY KEY (id) 16 | ); 17 | 18 | CREATE TABLE BOARDING_PASS ( 19 | flight_no VARCHAR(8) NOT NULL, 20 | seq_no INT NOT NULL, 21 | passenger VARCHAR(1000), 22 | seat CHAR(3), 23 | PRIMARY KEY (flight_no, seq_no) 24 | ); 25 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/derby/JdbcRepositoryManyToOneDerbyTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.derby; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManyToOneTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestDerbyConfig.class) 12 | public class JdbcRepositoryManyToOneDerbyTest extends JdbcRepositoryManyToOneTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/h2/JdbcRepositoryGeneratedKeyH2Test.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.h2; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryGeneratedKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestH2Config.class) 12 | public class JdbcRepositoryGeneratedKeyH2Test extends JdbcRepositoryGeneratedKeyTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/derby/JdbcRepositoryCompoundPkDerbyTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.derby; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryCompoundPkTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestDerbyConfig.class) 12 | public class JdbcRepositoryCompoundPkDerbyTest extends JdbcRepositoryCompoundPkTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/hsqldb/JdbcRepositoryManualKeyHsqldbTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.hsqldb; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManualKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:19 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestHsqldbConfig.class) 12 | public class JdbcRepositoryManualKeyHsqldbTest extends JdbcRepositoryManualKeyTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/hsqldb/JdbcRepositoryManyToOneHsqldbTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.hsqldb; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManyToOneTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestHsqldbConfig.class) 12 | public class JdbcRepositoryManyToOneHsqldbTest extends JdbcRepositoryManyToOneTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/hsqldb/JdbcRepositoryCompoundPkHsqldbTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.hsqldb; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryCompoundPkTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestHsqldbConfig.class) 12 | public class JdbcRepositoryCompoundPkHsqldbTest extends JdbcRepositoryCompoundPkTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/derby/JdbcRepositoryGeneratedKeyDerbyTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.derby; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryGeneratedKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestDerbyConfig.class) 12 | public class JdbcRepositoryGeneratedKeyDerbyTest extends JdbcRepositoryGeneratedKeyTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/hsqldb/JdbcRepositoryGeneratedKeyHsqldbTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.hsqldb; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryGeneratedKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestHsqldbConfig.class) 12 | public class JdbcRepositoryGeneratedKeyHsqldbTest extends JdbcRepositoryGeneratedKeyTest { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/schema_hsqldb.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE USERS ( 2 | user_name varchar(256) PRIMARY KEY, 3 | date_of_birth TIMESTAMP NOT NULL, 4 | reputation INT NOT NULL, 5 | enabled BOOLEAN NOT NULL 6 | ); 7 | 8 | CREATE TABLE COMMENTS ( 9 | id INT IDENTITY PRIMARY KEY, 10 | user_name varchar(256), 11 | contents varchar(1000), 12 | created_time TIMESTAMP NOT NULL, 13 | favourite_count INT NOT NULL, 14 | FOREIGN KEY (user_name) REFERENCES USERS(user_name) 15 | ); 16 | 17 | CREATE TABLE BOARDING_PASS ( 18 | flight_no varchar(8) NOT NULL, 19 | seq_no INT NOT NULL, 20 | passenger VARCHAR(1000), 21 | seat CHAR(3), 22 | PRIMARY KEY (flight_no, seq_no) 23 | ); 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/sql/MssqlSqlGenerator.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.sql; 2 | 3 | import com.nurkiewicz.jdbcrepository.TableDescription; 4 | import org.springframework.data.domain.Pageable; 5 | 6 | /** 7 | * Author: tom 8 | */ 9 | public class MssqlSqlGenerator extends AbstractMssqlSqlGenerator { 10 | public MssqlSqlGenerator() { 11 | } 12 | 13 | public MssqlSqlGenerator(String allColumnsClause) { 14 | super(allColumnsClause); 15 | } 16 | 17 | 18 | @Override 19 | public String selectAll(TableDescription table, Pageable page) { 20 | return SQL99Helper.generateSelectAllWithPagination(table, page, this); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources/schema_derby.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE USERS ( 2 | user_name varchar(256) NOT NULL PRIMARY KEY, 3 | date_of_birth TIMESTAMP NOT NULL, 4 | reputation INT NOT NULL, 5 | enabled BOOLEAN NOT NULL 6 | ); 7 | 8 | CREATE TABLE COMMENTS ( 9 | id INT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY, 10 | user_name varchar(256), 11 | contents varchar(1000), 12 | created_time TIMESTAMP NOT NULL, 13 | favourite_count INT NOT NULL, 14 | FOREIGN KEY (user_name) REFERENCES USERS(user_name) 15 | ); 16 | 17 | CREATE TABLE BOARDING_PASS ( 18 | flight_no varchar(8) NOT NULL, 19 | seq_no INT NOT NULL, 20 | passenger VARCHAR(1000), 21 | seat CHAR(3), 22 | PRIMARY KEY (flight_no, seq_no) 23 | ); 24 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/derby/JdbcRepositoryManualKeyDerbyTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.derby; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManualKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:19 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestDerbyConfig.class) 12 | public class JdbcRepositoryManualKeyDerbyTest extends JdbcRepositoryManualKeyTest { 13 | public JdbcRepositoryManualKeyDerbyTest() { 14 | super(-1); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/schema_h2.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS USERS ( 2 | user_name varchar(256) PRIMARY KEY, 3 | date_of_birth DATE NOT NULL, 4 | reputation INT NOT NULL, 5 | enabled BOOLEAN NOT NULL 6 | ); 7 | 8 | CREATE TABLE IF NOT EXISTS COMMENTS ( 9 | id INT AUTO_INCREMENT PRIMARY KEY, 10 | user_name varchar(256), 11 | contents varchar(1000), 12 | created_time TIMESTAMP NOT NULL, 13 | favourite_count INT NOT NULL, 14 | FOREIGN KEY (user_name) REFERENCES USERS(user_name) 15 | ); 16 | 17 | CREATE TABLE IF NOT EXISTS BOARDING_PASS ( 18 | flight_no varchar(8) NOT NULL, 19 | seq_no INT NOT NULL, 20 | passenger VARCHAR(1000), 21 | seat CHAR(3), 22 | PRIMARY KEY (flight_no, seq_no) 23 | ); 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/sql/OracleSqlGenerator.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.sql; 2 | 3 | import com.nurkiewicz.jdbcrepository.TableDescription; 4 | import org.springframework.data.domain.Pageable; 5 | 6 | /** 7 | * Author: tom 8 | */ 9 | public class OracleSqlGenerator extends SqlGenerator { 10 | public OracleSqlGenerator() { 11 | } 12 | 13 | public OracleSqlGenerator(String allColumnsClause) { 14 | super(allColumnsClause); 15 | } 16 | 17 | @Override 18 | protected String limitClause(Pageable page) { 19 | return ""; 20 | } 21 | 22 | @Override 23 | public String selectAll(TableDescription table, Pageable page) { 24 | return SQL99Helper.generateSelectAllWithPagination(table, page, this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/schema_postgresql.sql: -------------------------------------------------------------------------------- 1 | CREATE SEQUENCE user_seq; 2 | 3 | CREATE TABLE USERS ( 4 | user_name varchar(255) PRIMARY KEY DEFAULT nextval('user_seq'), 5 | date_of_birth DATE NOT NULL, 6 | reputation INT NOT NULL, 7 | enabled BOOLEAN NOT NULL 8 | ); 9 | 10 | CREATE SEQUENCE comment_seq; 11 | 12 | CREATE TABLE IF NOT EXISTS COMMENTS ( 13 | id INT PRIMARY KEY DEFAULT nextval('comment_seq'), 14 | user_name varchar(256) REFERENCES USERS, 15 | contents varchar(1000), 16 | created_time TIMESTAMP NOT NULL, 17 | favourite_count INT NOT NULL 18 | ); 19 | 20 | CREATE TABLE BOARDING_PASS ( 21 | flight_no VARCHAR(8) NOT NULL, 22 | seq_no INT NOT NULL, 23 | passenger VARCHAR(1000), 24 | seat CHAR(3), 25 | PRIMARY KEY (flight_no, seq_no) 26 | ); -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql/JdbcRepositoryManyToOneMssqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManyToOneTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMssqlConfig.class) 12 | public class JdbcRepositoryManyToOneMssqlTest extends JdbcRepositoryManyToOneTest { 13 | 14 | public JdbcRepositoryManyToOneMssqlTest() { 15 | super(JdbcRepositoryTestMssqlConfig.MSSQL_PORT); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql/JdbcRepositoryCompoundPkMssqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryCompoundPkTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMssqlConfig.class) 12 | public class JdbcRepositoryCompoundPkMssqlTest extends JdbcRepositoryCompoundPkTest { 13 | 14 | public JdbcRepositoryCompoundPkMssqlTest() { 15 | super(JdbcRepositoryTestMssqlConfig.MSSQL_PORT); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql/JdbcRepositoryManualKeyMssqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManualKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:19 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMssqlConfig.class) 12 | public class JdbcRepositoryManualKeyMssqlTest extends JdbcRepositoryManualKeyTest { 13 | 14 | public JdbcRepositoryManualKeyMssqlTest() { 15 | super(JdbcRepositoryTestMssqlConfig.MSSQL_PORT); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/oracle/JdbcRepositoryManyToOneOracleTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.oracle; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManyToOneTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestOracleConfig.class) 12 | public class JdbcRepositoryManyToOneOracleTest extends JdbcRepositoryManyToOneTest { 13 | 14 | public JdbcRepositoryManyToOneOracleTest() { 15 | super(JdbcRepositoryTestOracleConfig.ORACLE_PORT); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/oracle/JdbcRepositoryCompoundPkOracleTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.oracle; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryCompoundPkTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestOracleConfig.class) 12 | public class JdbcRepositoryCompoundPkOracleTest extends JdbcRepositoryCompoundPkTest { 13 | 14 | public JdbcRepositoryCompoundPkOracleTest() { 15 | super(JdbcRepositoryTestOracleConfig.ORACLE_PORT); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/oracle/JdbcRepositoryManualKeyOracleTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.oracle; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManualKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:19 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestOracleConfig.class) 12 | public class JdbcRepositoryManualKeyOracleTest extends JdbcRepositoryManualKeyTest { 13 | 14 | public JdbcRepositoryManualKeyOracleTest() { 15 | super(JdbcRepositoryTestOracleConfig.ORACLE_PORT); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql/JdbcRepositoryGeneratedKeyMssqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryGeneratedKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMssqlConfig.class) 12 | public class JdbcRepositoryGeneratedKeyMssqlTest extends JdbcRepositoryGeneratedKeyTest { 13 | 14 | public JdbcRepositoryGeneratedKeyMssqlTest() { 15 | super(JdbcRepositoryTestMssqlConfig.MSSQL_PORT); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql2012/JdbcRepositoryManyToOneMssql2012Test.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql2012; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManyToOneTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMssql2012Config.class) 12 | public class JdbcRepositoryManyToOneMssql2012Test extends JdbcRepositoryManyToOneTest { 13 | 14 | public JdbcRepositoryManyToOneMssql2012Test() { 15 | super(JdbcRepositoryTestMssql2012Config.MSSQL_PORT); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/oracle/JdbcRepositoryGeneratedKeyOracleTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.oracle; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryGeneratedKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestOracleConfig.class) 12 | public class JdbcRepositoryGeneratedKeyOracleTest extends JdbcRepositoryGeneratedKeyTest { 13 | 14 | public JdbcRepositoryGeneratedKeyOracleTest() { 15 | super(JdbcRepositoryTestOracleConfig.ORACLE_PORT); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql2012/JdbcRepositoryCompoundPkMssql2012Test.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql2012; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryCompoundPkTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMssql2012Config.class) 12 | public class JdbcRepositoryCompoundPkMssql2012Test extends JdbcRepositoryCompoundPkTest { 13 | 14 | public JdbcRepositoryCompoundPkMssql2012Test() { 15 | super(JdbcRepositoryTestMssql2012Config.MSSQL_PORT); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql2012/JdbcRepositoryManualKeyMssql2012Test.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql2012; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManualKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:19 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMssql2012Config.class) 12 | public class JdbcRepositoryManualKeyMssql2012Test extends JdbcRepositoryManualKeyTest { 13 | 14 | public JdbcRepositoryManualKeyMssql2012Test() { 15 | super(JdbcRepositoryTestMssql2012Config.MSSQL_PORT); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql2012/JdbcRepositoryGeneratedKeyMssql2012Test.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql2012; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryGeneratedKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/9/13, 10:20 PM 10 | */ 11 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMssql2012Config.class) 12 | public class JdbcRepositoryGeneratedKeyMssql2012Test extends JdbcRepositoryGeneratedKeyTest { 13 | 14 | public JdbcRepositoryGeneratedKeyMssql2012Test() { 15 | super(JdbcRepositoryTestMssql2012Config.MSSQL_PORT); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mysql/JdbcRepositoryManyToOneMysqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mysql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManyToOneTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | import static com.nurkiewicz.jdbcrepository.mysql.JdbcRepositoryTestMysqlConfig.MYSQL_PORT; 8 | 9 | /** 10 | * @author Tomasz Nurkiewicz 11 | * @since 1/9/13, 10:20 PM 12 | */ 13 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMysqlConfig.class) 14 | public class JdbcRepositoryManyToOneMysqlTest extends JdbcRepositoryManyToOneTest { 15 | public JdbcRepositoryManyToOneMysqlTest() { 16 | super(MYSQL_PORT); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mysql/JdbcRepositoryCompoundPkMysqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mysql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryCompoundPkTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | import static com.nurkiewicz.jdbcrepository.mysql.JdbcRepositoryTestMysqlConfig.MYSQL_PORT; 8 | 9 | /** 10 | * @author Tomasz Nurkiewicz 11 | * @since 1/9/13, 10:20 PM 12 | */ 13 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMysqlConfig.class) 14 | public class JdbcRepositoryCompoundPkMysqlTest extends JdbcRepositoryCompoundPkTest { 15 | public JdbcRepositoryCompoundPkMysqlTest() { 16 | super(MYSQL_PORT); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mysql/JdbcRepositoryManualKeyMysqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mysql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManualKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | import static com.nurkiewicz.jdbcrepository.mysql.JdbcRepositoryTestMysqlConfig.MYSQL_PORT; 8 | 9 | /** 10 | * @author Tomasz Nurkiewicz 11 | * @since 1/9/13, 10:19 PM 12 | */ 13 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMysqlConfig.class) 14 | public class JdbcRepositoryManualKeyMysqlTest extends JdbcRepositoryManualKeyTest { 15 | 16 | public JdbcRepositoryManualKeyMysqlTest() { 17 | super(MYSQL_PORT); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/h2/JdbcRepositoryTestH2Config.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.h2; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryTestConfig; 4 | import org.h2.jdbcx.JdbcDataSource; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.transaction.annotation.EnableTransactionManagement; 8 | 9 | import javax.sql.DataSource; 10 | 11 | @EnableTransactionManagement 12 | @Configuration 13 | public class JdbcRepositoryTestH2Config extends JdbcRepositoryTestConfig { 14 | 15 | @Bean 16 | @Override 17 | public DataSource dataSource() { 18 | JdbcDataSource ds = new JdbcDataSource(); 19 | ds.setURL("jdbc:h2:mem:DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'classpath:schema_h2.sql'"); 20 | return ds; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mysql/JdbcRepositoryGeneratedKeyMysqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mysql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryGeneratedKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | import static com.nurkiewicz.jdbcrepository.mysql.JdbcRepositoryTestMysqlConfig.MYSQL_PORT; 8 | 9 | /** 10 | * @author Tomasz Nurkiewicz 11 | * @since 1/9/13, 10:20 PM 12 | */ 13 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestMysqlConfig.class) 14 | public class JdbcRepositoryGeneratedKeyMysqlTest extends JdbcRepositoryGeneratedKeyTest { 15 | 16 | public JdbcRepositoryGeneratedKeyMysqlTest() { 17 | super(MYSQL_PORT); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/postgresql/JdbcRepositoryManyToOnePostgresqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.postgresql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManyToOneTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | import static com.nurkiewicz.jdbcrepository.postgresql.JdbcRepositoryTestPostgresqlConfig.POSTGRESQL_PORT; 8 | 9 | /** 10 | * @author Tomasz Nurkiewicz 11 | * @since 1/9/13, 10:20 PM 12 | */ 13 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestPostgresqlConfig.class) 14 | public class JdbcRepositoryManyToOnePostgresqlTest extends JdbcRepositoryManyToOneTest { 15 | public JdbcRepositoryManyToOnePostgresqlTest() { 16 | super(POSTGRESQL_PORT); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/postgresql/JdbcRepositoryCompoundPkPostgresqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.postgresql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryCompoundPkTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | import static com.nurkiewicz.jdbcrepository.postgresql.JdbcRepositoryTestPostgresqlConfig.POSTGRESQL_PORT; 8 | 9 | /** 10 | * @author Tomasz Nurkiewicz 11 | * @since 1/9/13, 10:20 PM 12 | */ 13 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestPostgresqlConfig.class) 14 | public class JdbcRepositoryCompoundPkPostgresqlTest extends JdbcRepositoryCompoundPkTest { 15 | public JdbcRepositoryCompoundPkPostgresqlTest() { 16 | super(POSTGRESQL_PORT); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/postgresql/JdbcRepositoryManualKeyPostgresqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.postgresql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryManualKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | import static com.nurkiewicz.jdbcrepository.postgresql.JdbcRepositoryTestPostgresqlConfig.POSTGRESQL_PORT; 8 | 9 | /** 10 | * @author Tomasz Nurkiewicz 11 | * @since 1/9/13, 10:19 PM 12 | */ 13 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestPostgresqlConfig.class) 14 | public class JdbcRepositoryManualKeyPostgresqlTest extends JdbcRepositoryManualKeyTest { 15 | 16 | public JdbcRepositoryManualKeyPostgresqlTest() { 17 | super(POSTGRESQL_PORT); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/postgresql/JdbcRepositoryGeneratedKeyPostgresqlTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.postgresql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryGeneratedKeyTest; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 6 | 7 | import static com.nurkiewicz.jdbcrepository.postgresql.JdbcRepositoryTestPostgresqlConfig.POSTGRESQL_PORT; 8 | 9 | /** 10 | * @author Tomasz Nurkiewicz 11 | * @since 1/9/13, 10:20 PM 12 | */ 13 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = JdbcRepositoryTestPostgresqlConfig.class) 14 | public class JdbcRepositoryGeneratedKeyPostgresqlTest extends JdbcRepositoryGeneratedKeyTest { 15 | 16 | public JdbcRepositoryGeneratedKeyPostgresqlTest() { 17 | super(POSTGRESQL_PORT); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/hsqldb/JdbcRepositoryTestHsqldbConfig.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.hsqldb; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryTestConfig; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 7 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 8 | import org.springframework.transaction.annotation.EnableTransactionManagement; 9 | 10 | import javax.sql.DataSource; 11 | 12 | @EnableTransactionManagement 13 | @Configuration 14 | public class JdbcRepositoryTestHsqldbConfig extends JdbcRepositoryTestConfig { 15 | 16 | @Bean 17 | @Override 18 | public DataSource dataSource() { 19 | return new EmbeddedDatabaseBuilder(). 20 | addScript("schema_hsqldb.sql"). 21 | setType(EmbeddedDatabaseType.H2). 22 | build(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/sql/SQL99Helper.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.sql; 2 | 3 | import com.nurkiewicz.jdbcrepository.TableDescription; 4 | import org.springframework.data.domain.Pageable; 5 | 6 | /** 7 | * Author: tom 8 | */ 9 | public class SQL99Helper { 10 | public static String ROW_NUM_WRAPPER = "SELECT a__.* FROM (SELECT row_number() OVER (ORDER BY %s) AS ROW_NUM, t__.* FROM (%s) t__) a__ WHERE a__.row_num BETWEEN %s AND %s"; 11 | 12 | public static String generateSelectAllWithPagination(TableDescription table, Pageable page, SqlGenerator sqlGenerator) { 13 | final int beginOffset = page.getPageNumber() * page.getPageSize() + 1; 14 | final int endOffset = beginOffset + page.getPageSize() - 1; 15 | String orderByPart = page.getSort() != null ? page.getSort().toString().replace(":", "") : table.getIdColumns().get(0); 16 | String selectAllPart = sqlGenerator.selectAll(table); 17 | return String.format(ROW_NUM_WRAPPER, orderByPart, selectAllPart, beginOffset, endOffset); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mysql/JdbcRepositoryTestMysqlConfig.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mysql; 2 | 3 | import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource; 4 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryTestConfig; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.transaction.annotation.EnableTransactionManagement; 8 | 9 | import javax.sql.DataSource; 10 | 11 | @EnableTransactionManagement 12 | @Configuration 13 | public class JdbcRepositoryTestMysqlConfig extends JdbcRepositoryTestConfig { 14 | 15 | public static final int MYSQL_PORT = 3306; 16 | 17 | @Bean 18 | @Override 19 | public DataSource dataSource() { 20 | MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); 21 | ds.setUser("root"); 22 | ds.setPassword(System.getProperty("mysql.password", "")); 23 | ds.setDatabaseName("spring_data_jdbc_repository_test"); 24 | return ds; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/sql/DerbySqlGenerator.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.sql; 2 | 3 | import com.nurkiewicz.jdbcrepository.TableDescription; 4 | import org.springframework.data.domain.Pageable; 5 | 6 | /** 7 | * @author Tomasz Nurkiewicz 8 | * @since 1/16/13, 10:25 PM 9 | */ 10 | public class DerbySqlGenerator extends SqlGenerator { 11 | 12 | public static final String ROW_NUM_COLUMN = "ROW_NUM"; 13 | public static final String ROW_NUM_COLUMN_CLAUSE = "SELECT * FROM (SELECT ROW_NUMBER() OVER () AS " + ROW_NUM_COLUMN + ", t.* FROM ("; 14 | 15 | public DerbySqlGenerator(String allColumnsClause) { 16 | super(allColumnsClause); 17 | } 18 | 19 | public DerbySqlGenerator() { 20 | } 21 | 22 | @Override 23 | public String selectAll(TableDescription table, Pageable page) { 24 | final int offset = page.getPageNumber() * page.getPageSize(); 25 | return super.selectAll(table, page) + " OFFSET " + offset + " ROWS FETCH NEXT " + page.getPageSize() + " ROWS ONLY"; 26 | } 27 | 28 | @Override 29 | protected String limitClause(Pageable page) { 30 | return ""; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/resources/schema_mssql.sql: -------------------------------------------------------------------------------- 1 | -- Schema can be used for MS SQLServer 2008 and 2012 2 | 3 | IF OBJECT_ID('USERS', 'U') IS NOT NULL 4 | DROP TABLE USERS; 5 | GO 6 | CREATE TABLE USERS ( 7 | user_name VARCHAR(255) PRIMARY KEY, 8 | date_of_birth DATE NOT NULL, --timestamp columns can not be used for explicit inserts 9 | reputation INT NOT NULL, 10 | enabled BIT NOT NULL 11 | ); 12 | 13 | IF OBJECT_ID('COMMENTS', 'U') IS NOT NULL 14 | DROP TABLE COMMENTS; 15 | GO 16 | CREATE TABLE COMMENTS ( 17 | id INT IDENTITY (1, 1) PRIMARY KEY, 18 | user_name VARCHAR(256), 19 | contents VARCHAR(1000), 20 | created_time DATETIME NOT NULL, --timestamp columns can not be used for explicit inserts 21 | favourite_count INT NOT NULL 22 | ); 23 | 24 | 25 | IF OBJECT_ID('BOARDING_PASS', 'U') IS NOT NULL 26 | DROP TABLE BOARDING_PASS; 27 | GO 28 | CREATE TABLE BOARDING_PASS ( 29 | flight_no VARCHAR(8) NOT NULL, 30 | seq_no INT NOT NULL, 31 | passenger VARCHAR(1000), 32 | seat CHAR(3) 33 | CONSTRAINT PK_BOARDING_PASS PRIMARY KEY CLUSTERED (flight_no, seq_no) 34 | ); 35 | -------------------------------------------------------------------------------- /src/test/resources/schema_oracle.sql: -------------------------------------------------------------------------------- 1 | DROP SEQUENCE comment_seq; 2 | DROP TABLE COMMENTS; 3 | DROP TABLE users; 4 | DROP TABLE BOARDING_PASS; 5 | 6 | 7 | CREATE TABLE USERS ( 8 | user_name VARCHAR(255), 9 | date_of_birth DATE NOT NULL, 10 | reputation INT NOT NULL, 11 | enabled INT NOT NULL, 12 | CONSTRAINT pk_users_user_name PRIMARY KEY (user_name) 13 | ); 14 | 15 | 16 | CREATE SEQUENCE comment_seq 17 | START WITH 1000 18 | INCREMENT BY 1 19 | NOCACHE 20 | ; 21 | 22 | CREATE TABLE COMMENTS ( 23 | id INT, 24 | user_name VARCHAR(256) REFERENCES USERS, 25 | contents VARCHAR(1000), 26 | created_time TIMESTAMP NOT NULL, 27 | favourite_count INT NOT NULL, 28 | CONSTRAINT pk_comment_id PRIMARY KEY (id) 29 | ); 30 | 31 | CREATE OR REPLACE TRIGGER COMMENT_ID_GEN 32 | BEFORE INSERT ON COMMENTS 33 | FOR EACH ROW 34 | BEGIN 35 | SELECT 36 | comment_seq.nextval 37 | INTO :new.id 38 | FROM dual; 39 | END; 40 | / 41 | 42 | 43 | CREATE TABLE BOARDING_PASS ( 44 | flight_no VARCHAR(8) NOT NULL, 45 | seq_no INT NOT NULL, 46 | passenger VARCHAR(1000), 47 | seat CHAR(3), 48 | CONSTRAINT pk_BOARDING_PASS_fn_sn PRIMARY KEY (flight_no, seq_no) 49 | ); -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/AbstractIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import org.junit.Assume; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 6 | import org.springframework.test.context.transaction.BeforeTransaction; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import java.io.IOException; 10 | import java.net.InetSocketAddress; 11 | import java.net.Socket; 12 | 13 | /** 14 | * @author Tomasz Nurkiewicz 15 | * @since 12/20/12, 10:56 PM 16 | */ 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | @Transactional 19 | public abstract class AbstractIntegrationTest { 20 | 21 | private final int databasePort; 22 | 23 | protected AbstractIntegrationTest() { 24 | this.databasePort = -1; 25 | } 26 | 27 | protected AbstractIntegrationTest(int databasePort) { 28 | this.databasePort = databasePort; 29 | } 30 | 31 | @BeforeTransaction 32 | public void ignoreIfDatabaseNotAvailable() { 33 | if (databasePort > 0) { 34 | try { 35 | final Socket socket = new Socket(); 36 | socket.connect(new InetSocketAddress("localhost", databasePort)); 37 | socket.close(); 38 | } catch (IOException e) { 39 | Assume.assumeNoException(e); 40 | } 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/sql/Mssql2012SqlGenerator.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.sql; 2 | 3 | import com.nurkiewicz.jdbcrepository.TableDescription; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.util.StringUtils; 6 | 7 | /** 8 | * SQLServer Pagination feature for SQLServer 2012+ -> extension of order by clause 9 | * 10 | * @see http://msdn.microsoft.com/en-us/library/ms188385.aspx 11 | * Author: tom 12 | */ 13 | public class Mssql2012SqlGenerator extends AbstractMssqlSqlGenerator { 14 | 15 | /** 16 | * Sort by first column 17 | */ 18 | private static final String MSSQL_DEFAULT_SORT_CLAUSE = " ORDER BY 1 ASC"; 19 | 20 | @Override 21 | public String selectAll(TableDescription table, Pageable page) { 22 | final int offset = page.getPageNumber() * page.getPageSize() + 1; 23 | String sortingClause = super.sortingClauseIfRequired(page.getSort()); 24 | 25 | if (!StringUtils.hasText(sortingClause)) { 26 | //The Pagination feature requires a sort clause, if none is given we sort by the first column 27 | sortingClause = MSSQL_DEFAULT_SORT_CLAUSE; 28 | } 29 | 30 | final String paginationClause = " OFFSET " + (offset - 1) + " ROWS FETCH NEXT " + page.getPageSize() + " ROW ONLY"; 31 | return super.selectAll(table) + sortingClause + paginationClause; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/TableDescription.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import org.springframework.util.Assert; 4 | import org.springframework.util.StringUtils; 5 | 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * @author Tomasz Nurkiewicz 12 | * @since 12/18/12, 10:06 PM 13 | */ 14 | public class TableDescription { 15 | 16 | private final String name; 17 | private final List idColumns; 18 | private final String fromClause; 19 | 20 | public TableDescription(String name, String fromClause, String... idColumns) { 21 | Assert.notNull(name); 22 | Assert.notNull(idColumns); 23 | Assert.isTrue(idColumns.length > 0, "At least one primary key column must be provided"); 24 | 25 | this.name = name; 26 | this.idColumns = Collections.unmodifiableList(Arrays.asList(idColumns)); 27 | if (StringUtils.hasText(fromClause)) { 28 | this.fromClause = fromClause; 29 | } else { 30 | this.fromClause = name; 31 | } 32 | } 33 | 34 | public TableDescription(String name, String idColumn) { 35 | this(name, null, idColumn); 36 | } 37 | 38 | public String getName() { 39 | return name; 40 | } 41 | 42 | public List getIdColumns() { 43 | return idColumns; 44 | } 45 | 46 | public String getFromClause() { 47 | return fromClause; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/JdbcRepositoryTestConfig.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import com.nurkiewicz.jdbcrepository.repositories.BoardingPassRepository; 4 | import com.nurkiewicz.jdbcrepository.repositories.CommentRepository; 5 | import com.nurkiewicz.jdbcrepository.repositories.CommentWithUserRepository; 6 | import com.nurkiewicz.jdbcrepository.repositories.UserRepository; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 9 | import org.springframework.transaction.PlatformTransactionManager; 10 | 11 | import javax.sql.DataSource; 12 | 13 | public abstract class JdbcRepositoryTestConfig { 14 | 15 | @Bean 16 | public abstract DataSource dataSource(); 17 | 18 | @Bean 19 | public CommentRepository commentRepository() { 20 | return new CommentRepository("COMMENTS"); 21 | } 22 | 23 | @Bean 24 | public UserRepository userRepository() { 25 | return new UserRepository("USERS"); 26 | } 27 | 28 | @Bean 29 | public BoardingPassRepository boardingPassRepository() { 30 | return new BoardingPassRepository(); 31 | } 32 | 33 | @Bean 34 | public CommentWithUserRepository commentWithUserRepository() { 35 | return new CommentWithUserRepository(new TableDescription("COMMENTS", "COMMENTS JOIN USERS ON COMMENTS.user_name = USERS.user_name", "id")); 36 | } 37 | 38 | @Bean 39 | public PlatformTransactionManager transactionManager() { 40 | return new DataSourceTransactionManager(dataSource()); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/repositories/CommentWithUser.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.repositories; 2 | 3 | import org.springframework.data.domain.Persistable; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/16/13, 10:51 PM 10 | */ 11 | public class CommentWithUser extends Comment implements Persistable { 12 | 13 | private User user; 14 | 15 | public CommentWithUser(User user, String contents, Date createdTime, int favouriteCount) { 16 | super(user.getUserName(), contents, createdTime, favouriteCount); 17 | this.user = user; 18 | } 19 | 20 | public CommentWithUser(Integer id, User user, String contents, Date createdTime, int favouriteCount) { 21 | super(id, user.getUserName(), contents, createdTime, favouriteCount); 22 | this.user = user; 23 | } 24 | 25 | public User getUser() { 26 | return user; 27 | } 28 | 29 | public void setUser(User user) { 30 | this.user = user; 31 | } 32 | 33 | @Override 34 | public boolean equals(Object o) { 35 | if (this == o) return true; 36 | if (!(o instanceof CommentWithUser)) return false; 37 | if (!super.equals(o)) return false; 38 | 39 | CommentWithUser that = (CommentWithUser) o; 40 | return user.equals(that.user); 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | int result = super.hashCode(); 46 | result = 31 * result + user.hashCode(); 47 | return result; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "CommentWithUser(id=" + getId() + ", user=" + user + ", contents='" + getContents() + '\'' + ", createdTime=" + getCreatedTime() + ", favouriteCount=" + getFavouriteCount() + ')'; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/postgresql/JdbcRepositoryTestPostgresqlConfig.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.postgresql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryTestConfig; 4 | import com.nurkiewicz.jdbcrepository.repositories.BoardingPassRepository; 5 | import com.nurkiewicz.jdbcrepository.repositories.CommentRepository; 6 | import com.nurkiewicz.jdbcrepository.repositories.UserRepository; 7 | import com.nurkiewicz.jdbcrepository.sql.PostgreSqlGenerator; 8 | import com.nurkiewicz.jdbcrepository.sql.SqlGenerator; 9 | import org.postgresql.jdbc2.optional.PoolingDataSource; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.transaction.annotation.EnableTransactionManagement; 13 | 14 | import javax.sql.DataSource; 15 | 16 | @EnableTransactionManagement 17 | @Configuration 18 | public class JdbcRepositoryTestPostgresqlConfig extends JdbcRepositoryTestConfig { 19 | 20 | public static final int POSTGRESQL_PORT = 5432; 21 | 22 | @Bean 23 | @Override 24 | public CommentRepository commentRepository() { 25 | return new CommentRepository("comments"); 26 | } 27 | 28 | @Bean 29 | @Override 30 | public UserRepository userRepository() { 31 | return new UserRepository("users"); 32 | } 33 | 34 | @Override 35 | public BoardingPassRepository boardingPassRepository() { 36 | return new BoardingPassRepository("boarding_pass"); 37 | } 38 | 39 | @Bean 40 | public SqlGenerator sqlGenerator() { 41 | return new PostgreSqlGenerator(); 42 | } 43 | 44 | @Bean 45 | @Override 46 | public DataSource dataSource() { 47 | PoolingDataSource ds = new PoolingDataSource(); 48 | ds.setUser("postgres"); 49 | ds.setPassword(System.getProperty("postgresql.password")); 50 | ds.setDatabaseName("spring_data_jdbc_repository_test"); 51 | return ds; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/repositories/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.repositories; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepository; 4 | import com.nurkiewicz.jdbcrepository.RowUnmapper; 5 | import org.springframework.jdbc.core.RowMapper; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.sql.Date; 9 | import java.sql.ResultSet; 10 | import java.sql.SQLException; 11 | import java.util.LinkedHashMap; 12 | import java.util.Map; 13 | 14 | @Repository 15 | public class UserRepository extends JdbcRepository { 16 | 17 | public UserRepository(String tableName) { 18 | super(MAPPER, ROW_UNMAPPER, tableName, "user_name"); 19 | } 20 | 21 | public static final RowMapper MAPPER = new RowMapper() { 22 | @Override 23 | public User mapRow(ResultSet rs, int rowNum) throws SQLException { 24 | return new User( 25 | rs.getString("user_name"), 26 | rs.getDate("date_of_birth"), 27 | rs.getInt("reputation"), 28 | rs.getBoolean("enabled") 29 | ).withPersisted(true); 30 | } 31 | }; 32 | 33 | public static final RowUnmapper ROW_UNMAPPER = new RowUnmapper() { 34 | @Override 35 | public Map mapColumns(User t) { 36 | final LinkedHashMap columns = new LinkedHashMap(); 37 | columns.put("user_name", t.getUserName()); 38 | columns.put("date_of_birth", new Date(t.getDateOfBirth().getTime())); 39 | columns.put("reputation", t.getReputation()); 40 | columns.put("enabled", t.isEnabled()); 41 | return columns; 42 | } 43 | }; 44 | 45 | @Override 46 | protected S postUpdate(S entity) { 47 | entity.withPersisted(true); 48 | return entity; 49 | } 50 | 51 | @Override 52 | protected S postCreate(S entity, Number generatedId) { 53 | entity.withPersisted(true); 54 | return entity; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/repositories/BoardingPassRepository.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.repositories; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepository; 4 | import com.nurkiewicz.jdbcrepository.RowUnmapper; 5 | import com.nurkiewicz.jdbcrepository.TableDescription; 6 | import org.springframework.jdbc.core.RowMapper; 7 | 8 | import java.sql.ResultSet; 9 | import java.sql.SQLException; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * @author Tomasz Nurkiewicz 15 | * @since 1/20/13, 10:09 AM 16 | */ 17 | public class BoardingPassRepository extends JdbcRepository { 18 | public BoardingPassRepository() { 19 | this("BOARDING_PASS"); 20 | } 21 | 22 | public BoardingPassRepository(String tableName) { 23 | super(MAPPER, UNMAPPER, new TableDescription(tableName, null, "flight_no", "seq_no") 24 | ); 25 | } 26 | 27 | @Override 28 | protected S postCreate(S entity, Number generatedId) { 29 | entity.withPersisted(true); 30 | return entity; 31 | } 32 | 33 | public static final RowMapper MAPPER = new RowMapper() { 34 | @Override 35 | public BoardingPass mapRow(ResultSet rs, int rowNum) throws SQLException { 36 | final BoardingPass boardingPass = new BoardingPass(); 37 | boardingPass.setFlightNo(rs.getString("flight_no")); 38 | boardingPass.setSeqNo(rs.getInt("seq_no")); 39 | boardingPass.setPassenger(rs.getString("passenger")); 40 | boardingPass.setSeat(rs.getString("seat")); 41 | return boardingPass.withPersisted(true); 42 | } 43 | }; 44 | 45 | public static final RowUnmapper UNMAPPER = new RowUnmapper() { 46 | @Override 47 | public Map mapColumns(BoardingPass boardingPass) { 48 | final HashMap map = new HashMap(); 49 | map.put("flight_no", boardingPass.getFlightNo()); 50 | map.put("seq_no", boardingPass.getSeqNo()); 51 | map.put("passenger", boardingPass.getPassenger()); 52 | map.put("seat", boardingPass.getSeat()); 53 | return map; 54 | 55 | } 56 | }; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/repositories/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.repositories; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepository; 4 | import com.nurkiewicz.jdbcrepository.RowUnmapper; 5 | import org.springframework.jdbc.core.RowMapper; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.sql.ResultSet; 9 | import java.sql.SQLException; 10 | import java.util.LinkedHashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * @author Tomasz Nurkiewicz 15 | * @since 12/17/12, 11:15 PM 16 | */ 17 | @Repository 18 | public class CommentRepository extends JdbcRepository { 19 | 20 | public CommentRepository(String tableName) { 21 | super(MAPPER, ROW_UNMAPPER, tableName); 22 | } 23 | 24 | public CommentRepository(RowMapper rowMapper, RowUnmapper rowUnmapper, String tableName, String idColumn) { 25 | super(rowMapper, rowUnmapper, tableName, idColumn); 26 | } 27 | 28 | public static final RowMapper MAPPER = new RowMapper() { 29 | 30 | @Override 31 | public Comment mapRow(ResultSet rs, int rowNum) throws SQLException { 32 | return new Comment( 33 | rs.getInt("id"), 34 | rs.getString("user_name"), 35 | rs.getString("contents"), 36 | rs.getTimestamp("created_time"), 37 | rs.getInt("favourite_count") 38 | ); 39 | } 40 | }; 41 | 42 | public static final RowUnmapper ROW_UNMAPPER = new RowUnmapper() { 43 | @Override 44 | public Map mapColumns(Comment comment) { 45 | Map mapping = new LinkedHashMap(); 46 | mapping.put("id", comment.getId()); 47 | mapping.put("user_name", comment.getUserName()); 48 | mapping.put("contents", comment.getContents()); 49 | mapping.put("created_time", new java.sql.Timestamp(comment.getCreatedTime().getTime())); 50 | mapping.put("favourite_count", comment.getFavouriteCount()); 51 | return mapping; 52 | } 53 | }; 54 | 55 | @Override 56 | protected S postCreate(S entity, Number generatedId) { 57 | entity.setId(generatedId.intValue()); 58 | return entity; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/oracle/JdbcRepositoryTestOracleConfig.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.oracle; 2 | 3 | import com.jolbox.bonecp.BoneCPDataSource; 4 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryTestConfig; 5 | import com.nurkiewicz.jdbcrepository.repositories.BoardingPassRepository; 6 | import com.nurkiewicz.jdbcrepository.repositories.CommentRepository; 7 | import com.nurkiewicz.jdbcrepository.repositories.UserRepository; 8 | import com.nurkiewicz.jdbcrepository.sql.OracleSqlGenerator; 9 | import com.nurkiewicz.jdbcrepository.sql.SqlGenerator; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.transaction.annotation.EnableTransactionManagement; 13 | 14 | import javax.sql.DataSource; 15 | 16 | @EnableTransactionManagement 17 | @Configuration 18 | public class JdbcRepositoryTestOracleConfig extends JdbcRepositoryTestConfig { 19 | 20 | public static final int ORACLE_PORT = Integer.parseInt(System.getProperty("oracle.port", "1521")); 21 | 22 | @Bean 23 | @Override 24 | public CommentRepository commentRepository() { 25 | return new CommentRepository("comments"); 26 | } 27 | 28 | @Bean 29 | @Override 30 | public UserRepository userRepository() { 31 | return new UserRepository("users"); 32 | } 33 | 34 | @Override 35 | public BoardingPassRepository boardingPassRepository() { 36 | return new BoardingPassRepository("boarding_pass"); 37 | } 38 | 39 | @Bean 40 | public SqlGenerator sqlGenerator() { 41 | return new OracleSqlGenerator(); 42 | } 43 | 44 | @Bean 45 | @Override 46 | public DataSource dataSource() { 47 | BoneCPDataSource ds = new BoneCPDataSource(); 48 | ds.setDriverClass("oracle.jdbc.OracleDriver"); 49 | final String host = System.getProperty("oracle.hostname", "localhost"); 50 | final String service = System.getProperty("oracle.sid", "orcl"); 51 | final String url = " jdbc:oracle:thin:@//" + host + ":" + ORACLE_PORT + "/" + service; 52 | ds.setJdbcUrl(url); 53 | ds.setUsername(System.getProperty("oracle.username")); 54 | ds.setPassword(System.getProperty("oracle.password")); 55 | return ds; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/StandaloneUsageTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import com.nurkiewicz.jdbcrepository.repositories.User; 4 | import com.nurkiewicz.jdbcrepository.repositories.UserRepository; 5 | import com.nurkiewicz.jdbcrepository.sql.SqlGenerator; 6 | import org.h2.jdbcx.JdbcDataSource; 7 | import org.junit.Test; 8 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 9 | import org.springframework.transaction.TransactionStatus; 10 | import org.springframework.transaction.support.TransactionCallback; 11 | import org.springframework.transaction.support.TransactionOperations; 12 | import org.springframework.transaction.support.TransactionTemplate; 13 | 14 | import java.util.Date; 15 | import java.util.List; 16 | 17 | import static org.fest.assertions.api.Assertions.assertThat; 18 | 19 | public class StandaloneUsageTest { 20 | 21 | public static final String JDBC_URL = "jdbc:h2:mem:DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'classpath:schema_h2.sql'"; 22 | 23 | @Test 24 | public void shouldStartRepositoryWithoutSpring() throws Exception { 25 | //given 26 | final JdbcDataSource dataSource = new JdbcDataSource(); 27 | dataSource.setURL(JDBC_URL); 28 | 29 | final UserRepository userRepository = new UserRepository("users"); 30 | userRepository.setDataSource(dataSource); 31 | userRepository.setSqlGenerator(new SqlGenerator()); //optional 32 | 33 | //when 34 | final List list = userRepository.findAll(); 35 | 36 | //then 37 | assertThat(list).isEmpty(); 38 | } 39 | 40 | @Test 41 | public void shouldInsertIntoDatabase() throws Exception { 42 | //given 43 | final JdbcDataSource dataSource = new JdbcDataSource(); 44 | dataSource.setURL(JDBC_URL); 45 | 46 | final UserRepository userRepository = new UserRepository("users"); 47 | userRepository.setDataSource(dataSource); 48 | userRepository.setSqlGenerator(new SqlGenerator()); 49 | 50 | //and 51 | final TransactionOperations tx = new TransactionTemplate( 52 | new DataSourceTransactionManager(dataSource)); 53 | 54 | //when 55 | final List users = tx.execute(new TransactionCallback>() { 56 | @Override 57 | public List doInTransaction(TransactionStatus status) { 58 | final User user = new User("john", new Date(), 0, false); 59 | userRepository.save(user); 60 | return userRepository.findAll(); 61 | } 62 | }); 63 | 64 | //then 65 | assertThat(users).hasSize(1); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/JdbcRepositoryGeneratedKeyTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import com.nurkiewicz.jdbcrepository.repositories.Comment; 4 | import com.nurkiewicz.jdbcrepository.repositories.CommentRepository; 5 | import com.nurkiewicz.jdbcrepository.repositories.User; 6 | import com.nurkiewicz.jdbcrepository.repositories.UserRepository; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.Date; 12 | 13 | import static org.fest.assertions.api.Assertions.assertThat; 14 | 15 | /** 16 | * @author Tomasz Nurkiewicz 17 | * @since 12/20/12, 10:55 PM 18 | */ 19 | public abstract class JdbcRepositoryGeneratedKeyTest extends AbstractIntegrationTest { 20 | 21 | @Resource 22 | private CommentRepository repository; 23 | 24 | @Resource 25 | private UserRepository userRepository; 26 | private String someUser = "some_user"; 27 | 28 | public JdbcRepositoryGeneratedKeyTest() { 29 | } 30 | 31 | public JdbcRepositoryGeneratedKeyTest(int databasePort) { 32 | super(databasePort); 33 | } 34 | 35 | @Before 36 | public void setup() { 37 | userRepository.save(new User(someUser, new Date(), -1, false)); 38 | } 39 | 40 | @Test 41 | public void shouldGenerateKey() throws Exception { 42 | //given 43 | final Comment comment = new Comment(someUser, "Some content", new Date(), 0); 44 | 45 | //when 46 | repository.save(comment); 47 | 48 | //then 49 | assertThat(comment.getId()).isNotNull(); 50 | } 51 | 52 | @Test 53 | public void shouldGenerateSubsequentIds() throws Exception { 54 | //given 55 | final Comment firstComment = new Comment(someUser, "Some content", new Date(), 0); 56 | final Comment secondComment = new Comment(someUser, "Some content", new Date(), 0); 57 | 58 | //when 59 | repository.save(firstComment); 60 | repository.save(secondComment); 61 | 62 | //then 63 | 64 | assertThat(firstComment.getId()).isLessThan(secondComment.getId()); 65 | } 66 | 67 | @Test 68 | public void shouldUpdateCommentByGeneratedId() throws Exception { 69 | //given 70 | final Date oldDate = new Date(100000000); 71 | final Date newDate = new Date(200000000); 72 | final Comment comment = repository.save(new Comment(someUser, "Some content", oldDate, 0)); 73 | final int id = comment.getId(); 74 | 75 | //when 76 | final Comment updatedComment = repository.save(new Comment(id, someUser, "New content", newDate, 1)); 77 | 78 | //then 79 | assertThat(repository.count()).isEqualTo(1); 80 | assertThat(updatedComment).isEqualTo(new Comment(id, someUser, "New content", newDate, 1)); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/repositories/CommentWithUserRepository.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.repositories; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepository; 4 | import com.nurkiewicz.jdbcrepository.RowUnmapper; 5 | import com.nurkiewicz.jdbcrepository.TableDescription; 6 | import com.nurkiewicz.jdbcrepository.sql.SqlGenerator; 7 | import org.springframework.jdbc.core.RowMapper; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import java.sql.ResultSet; 11 | import java.sql.SQLException; 12 | import java.util.LinkedHashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * @author Tomasz Nurkiewicz 17 | * @since 1/16/13, 10:52 PM 18 | */ 19 | @Repository 20 | public class CommentWithUserRepository extends JdbcRepository { 21 | 22 | public CommentWithUserRepository(TableDescription table) { 23 | this(MAPPER, ROW_UNMAPPER, table); 24 | } 25 | 26 | public CommentWithUserRepository(RowMapper rowMapper, RowUnmapper rowUnmapper, TableDescription table) { 27 | this(rowMapper, rowUnmapper, null, table); 28 | } 29 | 30 | public CommentWithUserRepository(RowMapper rowMapper, RowUnmapper rowUnmapper, SqlGenerator sqlGenerator, TableDescription table) { 31 | super(rowMapper, rowUnmapper, sqlGenerator, table); 32 | } 33 | 34 | public static final RowMapper MAPPER = new RowMapper() { 35 | 36 | @Override 37 | public CommentWithUser mapRow(ResultSet rs, int rowNum) throws SQLException { 38 | final User user = UserRepository.MAPPER.mapRow(rs, rowNum); 39 | return new CommentWithUser( 40 | rs.getInt("id"), 41 | user, 42 | rs.getString("contents"), 43 | rs.getTimestamp("created_time"), 44 | rs.getInt("favourite_count") 45 | ); 46 | } 47 | }; 48 | 49 | private static final RowUnmapper ROW_UNMAPPER = new RowUnmapper() { 50 | @Override 51 | public Map mapColumns(CommentWithUser comment) { 52 | Map mapping = new LinkedHashMap(); 53 | mapping.put("id", comment.getId()); 54 | mapping.put("user_name", comment.getUser().getUserName()); 55 | mapping.put("contents", comment.getContents()); 56 | mapping.put("created_time", new java.sql.Timestamp(comment.getCreatedTime().getTime())); 57 | mapping.put("favourite_count", comment.getFavouriteCount()); 58 | return mapping; 59 | } 60 | }; 61 | 62 | @Override 63 | protected S postCreate(S entity, Number generatedId) { 64 | entity.setId(generatedId.intValue()); 65 | return entity; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/repositories/User.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.repositories; 2 | 3 | import org.springframework.data.domain.Persistable; 4 | 5 | import java.util.Date; 6 | 7 | public class User implements Persistable { 8 | 9 | private transient boolean persisted; 10 | 11 | private String userName; 12 | 13 | private Date dateOfBirth; 14 | 15 | private int reputation; 16 | 17 | private boolean enabled; 18 | 19 | public User(String userName, Date dateOfBirth, int reputation, boolean enabled) { 20 | this.userName = userName; 21 | this.dateOfBirth = dateOfBirth; 22 | this.reputation = reputation; 23 | this.enabled = enabled; 24 | } 25 | 26 | @Override 27 | public String getId() { 28 | return userName; 29 | } 30 | 31 | @Override 32 | public boolean isNew() { 33 | return !persisted; 34 | } 35 | 36 | public String getUserName() { 37 | return userName; 38 | } 39 | 40 | public void setUserName(String userName) { 41 | this.userName = userName; 42 | } 43 | 44 | public Date getDateOfBirth() { 45 | return dateOfBirth; 46 | } 47 | 48 | public void setDateOfBirth(Date dateOfBirth) { 49 | this.dateOfBirth = dateOfBirth; 50 | } 51 | 52 | public int getReputation() { 53 | return reputation; 54 | } 55 | 56 | public void setReputation(int reputation) { 57 | this.reputation = reputation; 58 | } 59 | 60 | public boolean isEnabled() { 61 | return enabled; 62 | } 63 | 64 | public void setEnabled(boolean enabled) { 65 | this.enabled = enabled; 66 | } 67 | 68 | public User withPersisted(boolean persisted) { 69 | this.persisted = persisted; 70 | return this; 71 | } 72 | 73 | @Override 74 | public boolean equals(Object o) { 75 | if (this == o) return true; 76 | if (o == null || getClass() != o.getClass()) return false; 77 | 78 | User user = (User) o; 79 | 80 | if (enabled != user.enabled) return false; 81 | if (reputation != user.reputation) return false; 82 | if (dateOfBirth != null ? !dateOfBirth.equals(user.dateOfBirth) : user.dateOfBirth != null) return false; 83 | return !(userName != null ? !userName.equals(user.userName) : user.userName != null); 84 | 85 | } 86 | 87 | @Override 88 | public int hashCode() { 89 | int result = userName != null ? userName.hashCode() : 0; 90 | result = 31 * result + (dateOfBirth != null ? dateOfBirth.hashCode() : 0); 91 | result = 31 * result + reputation; 92 | result = 31 * result + (enabled ? 1 : 0); 93 | return result; 94 | } 95 | 96 | @Override 97 | public String toString() { 98 | return "User(userName='" + userName + '\'' + ", dateOfBirth=" + dateOfBirth + ", reputation=" + reputation + ", enabled=" + enabled + ')'; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/repositories/BoardingPass.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.repositories; 2 | 3 | import org.springframework.data.domain.Persistable; 4 | 5 | import static com.nurkiewicz.jdbcrepository.JdbcRepository.pk; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 1/20/13, 10:04 AM 10 | */ 11 | public class BoardingPass implements Persistable { 12 | 13 | private transient boolean persisted; 14 | 15 | private String flightNo; 16 | 17 | private int seqNo; 18 | 19 | private String passenger; 20 | 21 | private String seat; 22 | 23 | public BoardingPass() { 24 | } 25 | 26 | public BoardingPass(String flightNo, int seqNo, String passenger, String seat) { 27 | this.flightNo = flightNo; 28 | this.seqNo = seqNo; 29 | this.passenger = passenger; 30 | this.seat = seat; 31 | } 32 | 33 | public String getFlightNo() { 34 | return flightNo; 35 | } 36 | 37 | public void setFlightNo(String flightNo) { 38 | this.flightNo = flightNo; 39 | } 40 | 41 | public int getSeqNo() { 42 | return seqNo; 43 | } 44 | 45 | public void setSeqNo(int seqNo) { 46 | this.seqNo = seqNo; 47 | } 48 | 49 | public String getPassenger() { 50 | return passenger; 51 | } 52 | 53 | public void setPassenger(String passenger) { 54 | this.passenger = passenger; 55 | } 56 | 57 | public String getSeat() { 58 | return seat; 59 | } 60 | 61 | public void setSeat(String seat) { 62 | this.seat = seat; 63 | } 64 | 65 | @Override 66 | public Object[] getId() { 67 | return pk(flightNo, seqNo); 68 | } 69 | 70 | @Override 71 | public boolean isNew() { 72 | return !persisted; 73 | } 74 | 75 | public BoardingPass withPersisted(boolean persisted) { 76 | this.persisted = persisted; 77 | return this; 78 | } 79 | 80 | @Override 81 | public boolean equals(Object o) { 82 | if (this == o) return true; 83 | if (!(o instanceof BoardingPass)) return false; 84 | 85 | BoardingPass that = (BoardingPass) o; 86 | 87 | if (seqNo != that.seqNo) return false; 88 | if (flightNo != null ? !flightNo.equals(that.flightNo) : that.flightNo != null) return false; 89 | if (passenger != null ? !passenger.equals(that.passenger) : that.passenger != null) return false; 90 | return !(seat != null ? !seat.equals(that.seat) : that.seat != null); 91 | 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | int result = flightNo != null ? flightNo.hashCode() : 0; 97 | result = 31 * result + seqNo; 98 | result = 31 * result + (passenger != null ? passenger.hashCode() : 0); 99 | result = 31 * result + (seat != null ? seat.hashCode() : 0); 100 | return result; 101 | } 102 | 103 | @Override 104 | public String toString() { 105 | return "BoardingPass{flightNo='" + flightNo + '\'' + ", seqNo=" + seqNo + ", passenger='" + passenger + '\'' + ", seat='" + seat + '\'' + '}'; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql/JdbcRepositoryTestMssqlConfig.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryTestConfig; 4 | import com.nurkiewicz.jdbcrepository.RowUnmapper; 5 | import com.nurkiewicz.jdbcrepository.TableDescription; 6 | import com.nurkiewicz.jdbcrepository.repositories.BoardingPassRepository; 7 | import com.nurkiewicz.jdbcrepository.repositories.CommentRepository; 8 | import com.nurkiewicz.jdbcrepository.repositories.CommentWithUser; 9 | import com.nurkiewicz.jdbcrepository.repositories.CommentWithUserRepository; 10 | import com.nurkiewicz.jdbcrepository.repositories.UserRepository; 11 | import com.nurkiewicz.jdbcrepository.sql.MssqlSqlGenerator; 12 | import com.nurkiewicz.jdbcrepository.sql.SqlGenerator; 13 | import net.sourceforge.jtds.jdbcx.JtdsDataSource; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.transaction.annotation.EnableTransactionManagement; 17 | 18 | import javax.sql.DataSource; 19 | import java.sql.Timestamp; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | @EnableTransactionManagement 24 | @Configuration 25 | public class JdbcRepositoryTestMssqlConfig extends JdbcRepositoryTestConfig { 26 | 27 | public static final int MSSQL_PORT = Integer.parseInt(System.getProperty("mssql.port", "1433")); 28 | 29 | @Bean 30 | @Override 31 | public CommentRepository commentRepository() { 32 | return new CommentRepository("comments"); 33 | } 34 | 35 | @Bean 36 | @Override 37 | public UserRepository userRepository() { 38 | return new UserRepository("users"); 39 | } 40 | 41 | @Override 42 | public BoardingPassRepository boardingPassRepository() { 43 | return new BoardingPassRepository("boarding_pass"); 44 | } 45 | 46 | @Bean 47 | public SqlGenerator sqlGenerator() { 48 | return new MssqlSqlGenerator(); 49 | } 50 | 51 | @Override 52 | public CommentWithUserRepository commentWithUserRepository() { 53 | return new CommentWithUserRepository(CommentWithUserRepository.MAPPER, 54 | new RowUnmapper() { 55 | @Override 56 | public Map mapColumns(CommentWithUser comment) { 57 | Map mapping = new LinkedHashMap(); 58 | mapping.put("ID", comment.getId()); 59 | mapping.put("USER_NAME", comment.getUser().getUserName()); 60 | mapping.put("CONTENTS", comment.getContents()); 61 | mapping.put("CREATED_TIME", new Timestamp(comment.getCreatedTime().getTime())); 62 | mapping.put("FAVOURITE_COUNT", comment.getFavouriteCount()); 63 | return mapping; 64 | } 65 | }, 66 | new CommentWithUserMssqlGenerator(), 67 | new TableDescription("COMMENTS", "COMMENTS c JOIN USERS u ON c.USER_NAME = u.USER_NAME", "ID") 68 | ); 69 | } 70 | 71 | @Bean 72 | @Override 73 | public DataSource dataSource() { 74 | JtdsDataSource ds = new JtdsDataSource(); 75 | ds.setUser(System.getProperty("mssql.user", "unittest")); 76 | ds.setPassword(System.getProperty("mssql.password")); 77 | ds.setInstance(System.getProperty("mssql.instance")); 78 | ds.setServerName(System.getProperty("mssql.hostname", "localhost")); 79 | ds.setDatabaseName("spring_data_jdbc_repository_test"); 80 | return ds; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/derby/JdbcRepositoryTestDerbyConfig.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.derby; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryTestConfig; 4 | import com.nurkiewicz.jdbcrepository.RowUnmapper; 5 | import com.nurkiewicz.jdbcrepository.TableDescription; 6 | import com.nurkiewicz.jdbcrepository.repositories.Comment; 7 | import com.nurkiewicz.jdbcrepository.repositories.CommentRepository; 8 | import com.nurkiewicz.jdbcrepository.repositories.CommentWithUser; 9 | import com.nurkiewicz.jdbcrepository.repositories.CommentWithUserRepository; 10 | import com.nurkiewicz.jdbcrepository.sql.DerbySqlGenerator; 11 | import com.nurkiewicz.jdbcrepository.sql.SqlGenerator; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 15 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 16 | import org.springframework.transaction.annotation.EnableTransactionManagement; 17 | 18 | import javax.sql.DataSource; 19 | import java.sql.Timestamp; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | @EnableTransactionManagement 24 | @Configuration 25 | public class JdbcRepositoryTestDerbyConfig extends JdbcRepositoryTestConfig { 26 | 27 | @Override 28 | public CommentRepository commentRepository() { 29 | return new CommentRepository(CommentRepository.MAPPER, 30 | new RowUnmapper() { 31 | @Override 32 | public Map mapColumns(Comment comment) { 33 | Map mapping = new LinkedHashMap(); 34 | mapping.put("ID", comment.getId()); 35 | mapping.put("USER_NAME", comment.getUserName()); 36 | mapping.put("CONTENTS", comment.getContents()); 37 | mapping.put("CREATED_TIME", new java.sql.Timestamp(comment.getCreatedTime().getTime())); 38 | mapping.put("FAVOURITE_COUNT", comment.getFavouriteCount()); 39 | return mapping; 40 | } 41 | }, 42 | "COMMENTS", 43 | "ID" 44 | ); 45 | } 46 | 47 | @Override 48 | public CommentWithUserRepository commentWithUserRepository() { 49 | return new CommentWithUserRepository(CommentWithUserRepository.MAPPER, 50 | new RowUnmapper() { 51 | @Override 52 | public Map mapColumns(CommentWithUser comment) { 53 | Map mapping = new LinkedHashMap(); 54 | mapping.put("ID", comment.getId()); 55 | mapping.put("USER_NAME", comment.getUser().getUserName()); 56 | mapping.put("CONTENTS", comment.getContents()); 57 | mapping.put("CREATED_TIME", new Timestamp(comment.getCreatedTime().getTime())); 58 | mapping.put("FAVOURITE_COUNT", comment.getFavouriteCount()); 59 | return mapping; 60 | } 61 | }, 62 | new CommentWithUserDerbySqlGenerator(), 63 | new TableDescription("COMMENTS", "COMMENTS c JOIN USERS u ON c.USER_NAME = u.USER_NAME", "ID") 64 | ); 65 | } 66 | 67 | @Bean 68 | public SqlGenerator sqlGenerator() { 69 | return new DerbySqlGenerator(); 70 | } 71 | 72 | @Bean 73 | @Override 74 | public DataSource dataSource() { 75 | return new EmbeddedDatabaseBuilder(). 76 | addScript("schema_derby.sql"). 77 | setType(EmbeddedDatabaseType.DERBY). 78 | build(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/repositories/Comment.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.repositories; 2 | 3 | import org.springframework.data.domain.Persistable; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * @author Tomasz Nurkiewicz 9 | * @since 12/17/12, 11:12 PM 10 | */ 11 | public class Comment implements Persistable { 12 | 13 | private Integer id; 14 | 15 | private String userName; 16 | 17 | private String contents; 18 | 19 | private Date createdTime; 20 | 21 | private int favouriteCount; 22 | 23 | public Comment(String userName, String contents, Date createdTime, int favouriteCount) { 24 | this.userName = userName; 25 | this.contents = contents; 26 | this.createdTime = createdTime; 27 | this.favouriteCount = favouriteCount; 28 | } 29 | 30 | public Comment(Integer id, String userName, String contents, Date createdTime, int favouriteCount) { 31 | this(userName, contents, createdTime, favouriteCount); 32 | this.id = id; 33 | } 34 | 35 | @Override 36 | public Integer getId() { 37 | return id; 38 | } 39 | 40 | public void setId(Integer id) { 41 | this.id = id; 42 | } 43 | 44 | @Override 45 | public boolean isNew() { 46 | return id == null; 47 | } 48 | 49 | public String getUserName() { 50 | return userName; 51 | } 52 | 53 | public void setUserName(String userName) { 54 | this.userName = userName; 55 | } 56 | 57 | public String getContents() { 58 | return contents; 59 | } 60 | 61 | public void setContents(String contents) { 62 | this.contents = contents; 63 | } 64 | 65 | public Date getCreatedTime() { 66 | return createdTime; 67 | } 68 | 69 | public void setCreatedTime(Date createdTime) { 70 | this.createdTime = createdTime; 71 | } 72 | 73 | public int getFavouriteCount() { 74 | return favouriteCount; 75 | } 76 | 77 | public void setFavouriteCount(int favouriteCount) { 78 | this.favouriteCount = favouriteCount; 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return "Comment(id=" + id + ", userName='" + userName + '\'' + ", contents='" + contents + '\'' + ", createdTime=" + createdTime + ", favouriteCount=" + favouriteCount + ')'; 84 | } 85 | 86 | @Override 87 | public boolean equals(Object o) { 88 | if (this == o) return true; 89 | if (!(o instanceof Comment)) return false; 90 | 91 | Comment comment = (Comment) o; 92 | 93 | if (favouriteCount != comment.favouriteCount) return false; 94 | if (contents != null ? !contents.equals(comment.contents) : comment.contents != null) return false; 95 | if (createdTime != null ? !createdTime.equals(comment.createdTime) : comment.createdTime != null) return false; 96 | if (id != null ? !id.equals(comment.id) : comment.id != null) return false; 97 | if (userName != null ? !userName.equals(comment.userName) : comment.userName != null) return false; 98 | 99 | return true; 100 | } 101 | 102 | @Override 103 | public int hashCode() { 104 | int result = id != null ? id.hashCode() : 0; 105 | result = 31 * result + (userName != null ? userName.hashCode() : 0); 106 | result = 31 * result + (contents != null ? contents.hashCode() : 0); 107 | result = 31 * result + (createdTime != null ? createdTime.hashCode() : 0); 108 | result = 31 * result + favouriteCount; 109 | return result; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/mssql2012/JdbcRepositoryTestMssql2012Config.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.mssql2012; 2 | 3 | import com.nurkiewicz.jdbcrepository.JdbcRepositoryTestConfig; 4 | import com.nurkiewicz.jdbcrepository.RowUnmapper; 5 | import com.nurkiewicz.jdbcrepository.TableDescription; 6 | import com.nurkiewicz.jdbcrepository.mssql.CommentWithUserMssqlGenerator; 7 | import com.nurkiewicz.jdbcrepository.repositories.BoardingPassRepository; 8 | import com.nurkiewicz.jdbcrepository.repositories.CommentRepository; 9 | import com.nurkiewicz.jdbcrepository.repositories.CommentWithUser; 10 | import com.nurkiewicz.jdbcrepository.repositories.CommentWithUserRepository; 11 | import com.nurkiewicz.jdbcrepository.repositories.UserRepository; 12 | import com.nurkiewicz.jdbcrepository.sql.Mssql2012SqlGenerator; 13 | import com.nurkiewicz.jdbcrepository.sql.SqlGenerator; 14 | import net.sourceforge.jtds.jdbcx.JtdsDataSource; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.transaction.annotation.EnableTransactionManagement; 18 | 19 | import javax.sql.DataSource; 20 | import java.sql.Timestamp; 21 | import java.util.LinkedHashMap; 22 | import java.util.Map; 23 | 24 | @EnableTransactionManagement 25 | @Configuration 26 | public class JdbcRepositoryTestMssql2012Config extends JdbcRepositoryTestConfig { 27 | 28 | public static final int MSSQL_PORT = Integer.parseInt(System.getProperty("mssql2012.port", "1433")); 29 | 30 | @Bean 31 | @Override 32 | public CommentRepository commentRepository() { 33 | return new CommentRepository("comments"); 34 | } 35 | 36 | @Bean 37 | @Override 38 | public UserRepository userRepository() { 39 | return new UserRepository("users"); 40 | } 41 | 42 | @Override 43 | public BoardingPassRepository boardingPassRepository() { 44 | return new BoardingPassRepository("boarding_pass"); 45 | } 46 | 47 | @Bean 48 | public SqlGenerator sqlGenerator() { 49 | return new Mssql2012SqlGenerator(); 50 | } 51 | 52 | @Override 53 | public CommentWithUserRepository commentWithUserRepository() { 54 | return new CommentWithUserRepository(CommentWithUserRepository.MAPPER, 55 | new RowUnmapper() { 56 | @Override 57 | public Map mapColumns(CommentWithUser comment) { 58 | Map mapping = new LinkedHashMap(); 59 | mapping.put("ID", comment.getId()); 60 | mapping.put("USER_NAME", comment.getUser().getUserName()); 61 | mapping.put("CONTENTS", comment.getContents()); 62 | mapping.put("CREATED_TIME", new Timestamp(comment.getCreatedTime().getTime())); 63 | mapping.put("FAVOURITE_COUNT", comment.getFavouriteCount()); 64 | return mapping; 65 | } 66 | }, 67 | new CommentWithUserMssqlGenerator(), 68 | new TableDescription("COMMENTS", "COMMENTS c JOIN USERS u ON c.USER_NAME = u.USER_NAME", "ID") 69 | ); 70 | } 71 | 72 | @Bean 73 | @Override 74 | public DataSource dataSource() { 75 | JtdsDataSource ds = new JtdsDataSource(); 76 | ds.setUser(System.getProperty("mssql2012.user", "unittest")); 77 | ds.setPassword(System.getProperty("mssql2012.password")); 78 | ds.setInstance(System.getProperty("mssql2012.instance")); 79 | ds.setServerName(System.getProperty("mssql2012.hostname", "localhost")); 80 | ds.setDatabaseName("spring_data_jdbc_repository_test"); 81 | return ds; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/sql/SqlGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.sql; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.nurkiewicz.jdbcrepository.TableDescription; 5 | import org.junit.Test; 6 | 7 | import static org.fest.assertions.api.Assertions.assertThat; 8 | 9 | /** 10 | * @author Tomasz Nurkiewicz 11 | * @since 3/6/13, 8:32 PM 12 | */ 13 | public class SqlGeneratorTest { 14 | 15 | private final SqlGenerator sqlGenerator = new SqlGenerator(); 16 | 17 | @Test 18 | public void buildSqlForSelectByIdsWhenSingleIdColumnAndNoId() throws Exception { 19 | //given 20 | final TableDescription table = new TableDescription("table", "num"); 21 | 22 | //when 23 | final String sql = sqlGenerator.selectByIds(table, 0); 24 | 25 | //then 26 | assertThat(sql).isEqualTo("SELECT * FROM table"); 27 | } 28 | 29 | @Test 30 | public void buildSqlForSelectByIdsWhenSingleIdColumnAndOneId() throws Exception { 31 | //given 32 | final TableDescription table = new TableDescription("table", "num"); 33 | 34 | //when 35 | final String sql = sqlGenerator.selectByIds(table, 1); 36 | 37 | //then 38 | assertThat(sql).isEqualTo("SELECT * FROM table WHERE num = ?"); 39 | } 40 | 41 | @Test 42 | public void buildSqlForSelectByIdsWhenSingleIdColumnAndTwoIds() throws Exception { 43 | //given 44 | final TableDescription table = new TableDescription("table", "num"); 45 | 46 | //when 47 | final String sql = sqlGenerator.selectByIds(table, 2); 48 | 49 | //then 50 | assertThat(sql).isEqualTo("SELECT * FROM table WHERE num IN (?, ?)"); 51 | } 52 | 53 | @Test 54 | public void buildSqlForSelectByIdsWhenSingleIdColumnAndSeveralIds() throws Exception { 55 | //given 56 | final TableDescription table = new TableDescription("table", "num"); 57 | 58 | //when 59 | final String sql = sqlGenerator.selectByIds(table, 4); 60 | 61 | //then 62 | assertThat(sql).isEqualTo("SELECT * FROM table WHERE num IN (?, ?, ?, ?)"); 63 | } 64 | 65 | @Test 66 | public void buildSqlForSelectByIdsWhenMultipleIdColumnsAndNoId() throws Exception { 67 | //given 68 | final TableDescription table = new TableDescription("table", null, "num1", "num2", "num3"); 69 | 70 | //when 71 | final String sql = sqlGenerator.selectByIds(table, 0); 72 | 73 | //then 74 | assertThat(sql).isEqualTo("SELECT * FROM table"); 75 | } 76 | 77 | @Test 78 | public void buildSqlForSelectByIdsWhenMultipleIdColumnsAndOneId() throws Exception { 79 | //given 80 | final TableDescription table = new TableDescription("table", null, "num1", "num2", "num3"); 81 | 82 | //when 83 | final String sql = sqlGenerator.selectByIds(table, 1); 84 | 85 | //then 86 | assertThat(sql).isEqualTo("SELECT * FROM table WHERE num1 = ? AND num2 = ? AND num3 = ?"); 87 | } 88 | 89 | @Test 90 | public void buildSqlForSelectByIdsWhenMultipleIdColumnsAndTwoIds() throws Exception { 91 | //given 92 | final TableDescription table = new TableDescription("table", null, "num1", "num2", "num3"); 93 | 94 | //when 95 | final String sql = sqlGenerator.selectByIds(table, 2); 96 | 97 | //then 98 | assertThat(sql).isEqualTo("SELECT * FROM table WHERE (num1 = ? AND num2 = ? AND num3 = ?) OR (num1 = ? AND num2 = ? AND num3 = ?)"); 99 | } 100 | 101 | @Test 102 | public void buildSqlForSelectByIdsWhenMultipleIdColumnsAndSeveralIds() throws Exception { 103 | //given 104 | final TableDescription table = new TableDescription("table", null, "num1", "num2", "num3"); 105 | 106 | //when 107 | final String sql = sqlGenerator.selectByIds(table, 4); 108 | 109 | //then 110 | final String idClause = "(num1 = ? AND num2 = ? AND num3 = ?)"; 111 | assertThat(sql).isEqualTo("SELECT * FROM table WHERE " + idClause + " OR " + idClause + " OR " + idClause + " OR " + idClause); 112 | } 113 | 114 | @Test 115 | public void buildSqlForDeleteBySingleIdColumn() throws Exception { 116 | //given 117 | final TableDescription table = new TableDescription("table", "num"); 118 | 119 | //when 120 | final String sql = sqlGenerator.deleteById(table); 121 | 122 | //then 123 | assertThat(sql).isEqualTo("DELETE FROM table WHERE num = ?"); 124 | } 125 | 126 | @Test 127 | public void buildSqlForDeleteByTwoIdColumns() throws Exception { 128 | //given 129 | final TableDescription table = new TableDescription("table", null, "num1", "num2"); 130 | 131 | //when 132 | final String sql = sqlGenerator.deleteById(table); 133 | 134 | //then 135 | assertThat(sql).isEqualTo("DELETE FROM table WHERE num1 = ? AND num2 = ?"); 136 | } 137 | 138 | @Test 139 | public void buildSqlForDeleteByMultipleIdColumns() throws Exception { 140 | //given 141 | final TableDescription table = new TableDescription("table", null, "num1", "num2", "num3"); 142 | 143 | //when 144 | final String sql = sqlGenerator.deleteById(table); 145 | 146 | //then 147 | assertThat(sql).isEqualTo("DELETE FROM table WHERE num1 = ? AND num2 = ? AND num3 = ?"); 148 | } 149 | 150 | @Test 151 | public void buildSqlForUpdateWithSingleIdColumn() throws Exception { 152 | //given 153 | final Object ANY = new Object(); 154 | final TableDescription table = new TableDescription("table", "num"); 155 | 156 | //when 157 | final String sql = sqlGenerator.update(table, ImmutableMap.of("x", ANY, "y", ANY, "z", ANY)); 158 | 159 | //then 160 | assertThat(sql).isEqualTo("UPDATE table SET x = ?, y = ?, z = ? WHERE num = ?"); 161 | } 162 | 163 | @Test 164 | public void buildSqlForUpdateWithMultipleIdColumns() throws Exception { 165 | //given 166 | final Object ANY = new Object(); 167 | final TableDescription table = new TableDescription("table", null, "num1", "num2"); 168 | 169 | //when 170 | final String sql = sqlGenerator.update(table, ImmutableMap.of("x", ANY, "y", ANY, "z", ANY)); 171 | 172 | //then 173 | assertThat(sql).isEqualTo("UPDATE table SET x = ?, y = ?, z = ? WHERE num1 = ? AND num2 = ?"); 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/sql/SqlGenerator.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository.sql; 2 | 3 | import com.nurkiewicz.jdbcrepository.TableDescription; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.domain.Sort; 6 | 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | /** 13 | * @author Tomasz Nurkiewicz 14 | * @since 12/18/12, 9:18 PM 15 | */ 16 | public class SqlGenerator { 17 | 18 | public static final String WHERE = " WHERE "; 19 | public static final String AND = " AND "; 20 | public static final String OR = " OR "; 21 | public static final String SELECT = "SELECT "; 22 | public static final String FROM = "FROM "; 23 | public static final String DELETE = "DELETE "; 24 | public static final String COMMA = ", "; 25 | public static final String PARAM = " = ?"; 26 | private String allColumnsClause; 27 | 28 | public SqlGenerator(String allColumnsClause) { 29 | this.allColumnsClause = allColumnsClause; 30 | } 31 | 32 | public SqlGenerator() { 33 | this("*"); 34 | } 35 | 36 | public String count(TableDescription table) { 37 | return SELECT + "COUNT(*) " + FROM + table.getFromClause(); 38 | } 39 | 40 | public String deleteById(TableDescription table) { 41 | return DELETE + FROM + table.getName() + whereByIdClause(table); 42 | } 43 | 44 | private String whereByIdClause(TableDescription table) { 45 | final StringBuilder whereClause = new StringBuilder(WHERE); 46 | for (Iterator idColIterator = table.getIdColumns().iterator(); idColIterator.hasNext(); ) { 47 | whereClause.append(idColIterator.next()).append(PARAM); 48 | if (idColIterator.hasNext()) { 49 | whereClause.append(AND); 50 | } 51 | } 52 | return whereClause.toString(); 53 | } 54 | 55 | private String whereByIdsClause(TableDescription table, int idsCount) { 56 | final List idColumnNames = table.getIdColumns(); 57 | if (idColumnNames.size() > 1) { 58 | return whereByIdsWithMultipleIdColumns(idsCount, idColumnNames); 59 | } else { 60 | return whereByIdsWithSingleIdColumn(idsCount, idColumnNames.get(0)); 61 | } 62 | } 63 | 64 | private String whereByIdsWithMultipleIdColumns(int idsCount, List idColumnNames) { 65 | int idColumnsCount = idColumnNames.size(); 66 | final StringBuilder whereClause = new StringBuilder(WHERE); 67 | final int totalParams = idsCount * idColumnsCount; 68 | for (int idColumnIdx = 0; idColumnIdx < totalParams; idColumnIdx += idColumnsCount) { 69 | if (idColumnIdx > 0) { 70 | whereClause.append(OR); 71 | } 72 | whereClause.append("("); 73 | for (int i = 0; i < idColumnsCount; ++i) { 74 | if (i > 0) { 75 | whereClause.append(AND); 76 | } 77 | whereClause.append(idColumnNames.get(i)).append(" = ?"); 78 | } 79 | whereClause.append(")"); 80 | } 81 | return whereClause.toString(); 82 | } 83 | 84 | private String whereByIdsWithSingleIdColumn(int idsCount, String idColumn) { 85 | final StringBuilder whereClause = new StringBuilder(WHERE); 86 | return whereClause. 87 | append(idColumn). 88 | append(" IN ("). 89 | append(repeat("?", COMMA, idsCount)). 90 | append(")"). 91 | toString(); 92 | } 93 | 94 | public String selectAll(TableDescription table) { 95 | return SELECT + allColumnsClause + " " + FROM + table.getFromClause(); 96 | } 97 | 98 | public String selectAll(TableDescription table, Pageable page) { 99 | return selectAll(table, page.getSort()) + limitClause(page); 100 | } 101 | 102 | public String selectAll(TableDescription table, Sort sort) { 103 | return selectAll(table) + sortingClauseIfRequired(sort); 104 | } 105 | 106 | protected String limitClause(Pageable page) { 107 | final int offset = page.getPageNumber() * page.getPageSize(); 108 | return " LIMIT " + offset + COMMA + page.getPageSize(); 109 | } 110 | 111 | public String selectById(TableDescription table) { 112 | return selectAll(table) + whereByIdClause(table); 113 | } 114 | 115 | public String selectByIds(TableDescription table, int idsCount) { 116 | switch (idsCount) { 117 | case 0: 118 | return selectAll(table); 119 | case 1: 120 | return selectById(table); 121 | default: 122 | return selectAll(table) + whereByIdsClause(table, idsCount); 123 | } 124 | } 125 | 126 | protected String sortingClauseIfRequired(Sort sort) { 127 | if (sort == null) { 128 | return ""; 129 | } 130 | StringBuilder orderByClause = new StringBuilder(); 131 | orderByClause.append(" ORDER BY "); 132 | for(Iterator iterator = sort.iterator(); iterator.hasNext();) { 133 | final Sort.Order order = iterator.next(); 134 | orderByClause. 135 | append(order.getProperty()). 136 | append(" "). 137 | append(order.getDirection().toString()); 138 | if (iterator.hasNext()) { 139 | orderByClause.append(COMMA); 140 | } 141 | } 142 | return orderByClause.toString(); 143 | } 144 | 145 | public String update(TableDescription table, Map columns) { 146 | final StringBuilder updateQuery = new StringBuilder("UPDATE " + table.getName() + " SET "); 147 | for(Iterator> iterator = columns.entrySet().iterator(); iterator.hasNext();) { 148 | Map.Entry column = iterator.next(); 149 | updateQuery.append(column.getKey()).append(" = ?"); 150 | if (iterator.hasNext()) { 151 | updateQuery.append(COMMA); 152 | } 153 | } 154 | updateQuery.append(whereByIdClause(table)); 155 | return updateQuery.toString(); 156 | } 157 | 158 | public String create(TableDescription table, Map columns) { 159 | final StringBuilder createQuery = new StringBuilder("INSERT INTO " + table.getName() + " ("); 160 | appendColumnNames(createQuery, columns.keySet()); 161 | createQuery.append(")").append(" VALUES ("); 162 | createQuery.append(repeat("?", COMMA, columns.size())); 163 | return createQuery.append(")").toString(); 164 | } 165 | 166 | private void appendColumnNames(StringBuilder createQuery, Set columnNames) { 167 | for(Iterator iterator = columnNames.iterator(); iterator.hasNext();) { 168 | final String column = iterator.next(); 169 | createQuery.append(column); 170 | if (iterator.hasNext()) { 171 | createQuery.append(COMMA); 172 | } 173 | } 174 | } 175 | 176 | // Unfortunately {@link org.apache.commons.lang3.StringUtils} not available 177 | private static String repeat(String s, String separator, int count) { 178 | StringBuilder string = new StringBuilder((s.length() + separator.length()) * count); 179 | while (--count > 0) { 180 | string.append(s).append(separator); 181 | } 182 | return string.append(s).toString(); 183 | } 184 | 185 | 186 | public String deleteAll(TableDescription table) { 187 | return DELETE + FROM + table.getName(); 188 | } 189 | 190 | public String countById(TableDescription table) { 191 | return count(table) + whereByIdClause(table); 192 | } 193 | 194 | public String getAllColumnsClause() { 195 | return allColumnsClause; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/JdbcRepositoryManyToOneTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import com.nurkiewicz.jdbcrepository.repositories.CommentWithUser; 4 | import com.nurkiewicz.jdbcrepository.repositories.CommentWithUserRepository; 5 | import com.nurkiewicz.jdbcrepository.repositories.User; 6 | import com.nurkiewicz.jdbcrepository.repositories.UserRepository; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.PageRequest; 11 | import org.springframework.data.domain.Sort; 12 | 13 | import javax.annotation.Resource; 14 | import java.sql.Timestamp; 15 | import java.util.GregorianCalendar; 16 | import java.util.List; 17 | 18 | import static java.util.Calendar.JANUARY; 19 | import static org.fest.assertions.api.Assertions.assertThat; 20 | import static org.springframework.data.domain.Sort.Direction.ASC; 21 | import static org.springframework.data.domain.Sort.Direction.DESC; 22 | 23 | public abstract class JdbcRepositoryManyToOneTest extends AbstractIntegrationTest { 24 | 25 | public static final String SOME_USER = "some_user"; 26 | @Resource 27 | private CommentWithUserRepository repository; 28 | 29 | @Resource 30 | private UserRepository userRepository; 31 | private User someUser; 32 | private static final java.sql.Date SOME_DATE = new java.sql.Date(new GregorianCalendar(2013, JANUARY, 19).getTimeInMillis()); 33 | private static final Timestamp SOME_TIMESTAMP = new Timestamp(new GregorianCalendar(2013, JANUARY, 20).getTimeInMillis()); 34 | 35 | protected JdbcRepositoryManyToOneTest() { 36 | } 37 | 38 | protected JdbcRepositoryManyToOneTest(int databasePort) { 39 | super(databasePort); 40 | } 41 | 42 | @Before 43 | public void setup() { 44 | someUser = userRepository.save(new User(SOME_USER, SOME_DATE, -1, false)); 45 | } 46 | 47 | @Test 48 | public void shouldGenerateKey() throws Exception { 49 | //given 50 | final CommentWithUser comment = new CommentWithUser(someUser, "Some content", SOME_TIMESTAMP, 0); 51 | 52 | //when 53 | repository.save(comment); 54 | 55 | //then 56 | assertThat(comment.getId()).isNotNull(); 57 | } 58 | 59 | @Test 60 | public void shouldReturnCommentWithUserAttached() throws Exception { 61 | //given 62 | final CommentWithUser comment = new CommentWithUser(someUser, "Some content", SOME_TIMESTAMP, 0); 63 | 64 | //when 65 | repository.save(comment); 66 | 67 | //then 68 | final CommentWithUser foundComment = repository.findOne(comment.getId()); 69 | assertThat(foundComment).isEqualTo(new CommentWithUser(comment.getId(), someUser, "Some content", SOME_TIMESTAMP, 0)); 70 | } 71 | 72 | @Test 73 | public void shouldReturnMultipleCommentsAttachedToTheSameUser() throws Exception { 74 | //given 75 | final CommentWithUser first = repository.save(new CommentWithUser(someUser, "First comment", SOME_TIMESTAMP, 3)); 76 | final CommentWithUser second = repository.save(new CommentWithUser(someUser, "Second comment", SOME_TIMESTAMP, 2)); 77 | final CommentWithUser third = repository.save(new CommentWithUser(someUser, "Third comment", SOME_TIMESTAMP, 1)); 78 | 79 | //when 80 | final List all = repository.findAll(new Sort("favourite_count")); 81 | 82 | //then 83 | assertThat(all).containsExactly(third, second, first); 84 | } 85 | 86 | @Test 87 | public void shouldReturnMultipleCommentsAttachedToDifferentUsers() throws Exception { 88 | //given 89 | final User firstUser = userRepository.save(new User("First user", SOME_DATE, 10, false)); 90 | final User secondUser = userRepository.save(new User("Second user", SOME_DATE, 20, false)); 91 | final User thirdUser = userRepository.save(new User("Third user", SOME_DATE, 30, false)); 92 | 93 | final CommentWithUser first = repository.save(new CommentWithUser(firstUser, "First comment", SOME_TIMESTAMP, 3)); 94 | final CommentWithUser second = repository.save(new CommentWithUser(secondUser, "Second comment", SOME_TIMESTAMP, 2)); 95 | final CommentWithUser third = repository.save(new CommentWithUser(thirdUser, "Third comment", SOME_TIMESTAMP, 1)); 96 | 97 | //when 98 | final List all = repository.findAll(new Sort(DESC, "favourite_count")); 99 | 100 | //then 101 | assertThat(all).containsExactly(first, second, third); 102 | } 103 | 104 | @Test 105 | public void shouldReturnOnlyFirstPageWithUsers() throws Exception { 106 | //given 107 | final CommentWithUser first = repository.save(new CommentWithUser(someUser, "First comment", SOME_TIMESTAMP, 3)); 108 | final CommentWithUser second = repository.save(new CommentWithUser(someUser, "Second comment", SOME_TIMESTAMP, 2)); 109 | repository.save(new CommentWithUser(someUser, "Third comment", SOME_TIMESTAMP, 1)); 110 | 111 | //when 112 | final Page page = repository.findAll(new PageRequest(0, 2, ASC, "contents")); 113 | 114 | //then 115 | assertThat(page.getTotalElements()).isEqualTo(3); 116 | assertThat(page.getTotalPages()).isEqualTo(2); 117 | assertThat(page.getContent()).containsExactly(first, second); 118 | } 119 | 120 | @Test 121 | public void shouldReturnOnlySecondPageWithUsers() throws Exception { 122 | //given 123 | repository.save(new CommentWithUser(someUser, "First comment", SOME_TIMESTAMP, 3)); 124 | repository.save(new CommentWithUser(someUser, "Second comment", SOME_TIMESTAMP, 2)); 125 | final CommentWithUser third = repository.save(new CommentWithUser(someUser, "Third comment", SOME_TIMESTAMP, 1)); 126 | 127 | //when 128 | final Page page = repository.findAll(new PageRequest(1, 2, ASC, "contents")); 129 | 130 | //then 131 | assertThat(page.getTotalElements()).isEqualTo(3); 132 | assertThat(page.getTotalPages()).isEqualTo(2); 133 | assertThat(page.getContent()).containsExactly(third); 134 | } 135 | 136 | @Test 137 | public void shouldDeleteCommentWithoutDeletingUser() throws Exception { 138 | //given 139 | final CommentWithUser comment = repository.save(new CommentWithUser(someUser, "First comment", SOME_TIMESTAMP, 3)); 140 | 141 | //when 142 | repository.delete(comment); 143 | 144 | //then 145 | assertThat(repository.count()).isZero(); 146 | assertThat(userRepository.exists(SOME_USER)).isTrue(); 147 | } 148 | 149 | @Test 150 | public void shouldUpdateCommentByAttachingDifferentUser() throws Exception { 151 | //given 152 | final User firstUser = userRepository.save(new User("First user", SOME_DATE, 10, false)); 153 | final CommentWithUser comment = repository.save(new CommentWithUser(someUser, "First comment", SOME_TIMESTAMP, 3)); 154 | 155 | //when 156 | comment.setUser(firstUser); 157 | repository.save(comment); 158 | 159 | //then 160 | assertThat(repository.count()).isEqualTo(1); 161 | final CommentWithUser foundComment = repository.findOne(comment.getId()); 162 | assertThat(foundComment.getUser()).isEqualTo(firstUser); 163 | } 164 | 165 | @Test 166 | public void shouldDeleteAllCommentsWithoutDeletingUsers() throws Exception { 167 | //given 168 | final CommentWithUser first = repository.save(new CommentWithUser(someUser, "First comment", SOME_TIMESTAMP, 3)); 169 | final CommentWithUser second = repository.save(new CommentWithUser(someUser, "Second comment", SOME_TIMESTAMP, 2)); 170 | final CommentWithUser third = repository.save(new CommentWithUser(someUser, "Third comment", SOME_TIMESTAMP, 1)); 171 | 172 | //when 173 | repository.deleteAll(); 174 | 175 | //then 176 | assertThat(repository.count()).isZero(); 177 | assertThat(userRepository.exists(SOME_USER)).isTrue(); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/JdbcRepositoryCompoundPkTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.nurkiewicz.jdbcrepository.repositories.BoardingPass; 5 | import com.nurkiewicz.jdbcrepository.repositories.BoardingPassRepository; 6 | import org.junit.Test; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.data.domain.PageRequest; 9 | import org.springframework.data.domain.Sort; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.Arrays; 13 | import java.util.Collections; 14 | import java.util.Comparator; 15 | import java.util.List; 16 | 17 | import static com.nurkiewicz.jdbcrepository.JdbcRepository.pk; 18 | import static org.fest.assertions.api.Assertions.assertThat; 19 | import static org.springframework.data.domain.Sort.Direction.ASC; 20 | import static org.springframework.data.domain.Sort.Direction.DESC; 21 | import static org.springframework.data.domain.Sort.Order; 22 | 23 | public abstract class JdbcRepositoryCompoundPkTest extends AbstractIntegrationTest { 24 | 25 | public JdbcRepositoryCompoundPkTest() { 26 | } 27 | 28 | public JdbcRepositoryCompoundPkTest(int databasePort) { 29 | super(databasePort); 30 | } 31 | 32 | @Resource 33 | private BoardingPassRepository repository; 34 | 35 | @Test 36 | public void shouldStoreEntityWithCompoundPrimaryKey() throws Exception { 37 | //given 38 | final BoardingPass entity = new BoardingPass("FOO-100", 42, "Smith", "B01"); 39 | 40 | //when 41 | repository.save(entity); 42 | 43 | //then 44 | final BoardingPass found = repository.findOne(pk("FOO-100", 42)); 45 | assertThat(found).isEqualTo(new BoardingPass("FOO-100", 42, "Smith", "B01")); 46 | } 47 | 48 | @Test 49 | public void shouldAllowStoringMultipleEntitiesDifferingByOnePrimaryKeyColumn() throws Exception { 50 | //given 51 | repository.save(new BoardingPass("FOO-100", 1, "Smith", "B01")); 52 | repository.save(new BoardingPass("FOO-100", 2, "Johnson", "C02")); 53 | repository.save(new BoardingPass("BAR-100", 1, "Gordon", "D03")); 54 | repository.save(new BoardingPass("BAR-100", 2, "Who", "E04")); 55 | 56 | //when 57 | final BoardingPass foundFlight = repository.findOne(pk("BAR-100", 1)); 58 | 59 | //then 60 | assertThat(foundFlight).isEqualTo(new BoardingPass("BAR-100", 1, "Gordon", "D03")); 61 | } 62 | 63 | @Test 64 | public void shouldAllowUpdatingByPrimaryKey() throws Exception { 65 | //given 66 | repository.save(new BoardingPass("FOO-100", 1, "Smith", "B01")); 67 | final BoardingPass secondSeat = repository.save(new BoardingPass("FOO-100", 2, "Johnson", "C02")); 68 | 69 | secondSeat.setPassenger("Jameson"); 70 | secondSeat.setSeat("C03"); 71 | 72 | //when 73 | repository.save(secondSeat); 74 | 75 | //then 76 | assertThat(repository.count()).isEqualTo(2); 77 | final BoardingPass foundUpdated = repository.findOne(pk("FOO-100", 2)); 78 | assertThat(foundUpdated).isEqualTo(new BoardingPass("FOO-100", 2, "Jameson", "C03")); 79 | } 80 | 81 | @Test 82 | public void shouldDeleteByCompoundPrimaryKey() throws Exception { 83 | //given 84 | repository.save(new BoardingPass("FOO-100", 1, "Smith", "B01")); 85 | 86 | //when 87 | repository.delete(pk("FOO-100", 1)); 88 | 89 | //then 90 | assertThat(repository.exists(pk("FOO-100", 1))).isFalse(); 91 | } 92 | 93 | @Test 94 | public void shouldDeleteByEntityUsingCompoundPk() throws Exception { 95 | //given 96 | final BoardingPass bp = repository.save(new BoardingPass("FOO-100", 1, "Smith", "B01")); 97 | 98 | //when 99 | repository.delete(bp); 100 | 101 | //then 102 | assertThat(repository.findAll()).isEmpty(); 103 | } 104 | 105 | @Test 106 | public void shouldAllowSortingByAllPrimaryKeyColumns() throws Exception { 107 | //given 108 | repository.save(new BoardingPass("FOO-100", 1, "Smith", "B01")); 109 | repository.save(new BoardingPass("FOO-100", 2, "Johnson", "C02")); 110 | repository.save(new BoardingPass("BAR-100", 2, "Who", "E04")); 111 | repository.save(new BoardingPass("BAR-100", 1, "Gordon", "D03")); 112 | 113 | //when 114 | final List all = repository.findAll( 115 | new Sort( 116 | new Order(ASC, "flight_no"), 117 | new Order(DESC, "seq_no") 118 | ) 119 | ); 120 | 121 | //then 122 | assertThat(all).containsExactly( 123 | new BoardingPass("BAR-100", 2, "Who", "E04"), 124 | new BoardingPass("BAR-100", 1, "Gordon", "D03"), 125 | new BoardingPass("FOO-100", 2, "Johnson", "C02"), 126 | new BoardingPass("FOO-100", 1, "Smith", "B01") 127 | ); 128 | } 129 | 130 | @Test 131 | public void shouldReturnFirstPageOfSortedResults() throws Exception { 132 | //given 133 | repository.save(new BoardingPass("FOO-100", 1, "Smith", "B01")); 134 | repository.save(new BoardingPass("FOO-100", 2, "Johnson", "C02")); 135 | repository.save(new BoardingPass("BAR-100", 2, "Who", "E04")); 136 | repository.save(new BoardingPass("BAR-100", 1, "Gordon", "D03")); 137 | 138 | //when 139 | final Page page = repository.findAll( 140 | new PageRequest(0, 3, 141 | new Sort( 142 | new Order(ASC, "flight_no"), 143 | new Order(DESC, "seq_no") 144 | ) 145 | )); 146 | 147 | //then 148 | assertThat(page.getTotalElements()).isEqualTo(4); 149 | assertThat(page.getTotalPages()).isEqualTo(2); 150 | assertThat(page.getContent()).containsExactly( 151 | new BoardingPass("BAR-100", 2, "Who", "E04"), 152 | new BoardingPass("BAR-100", 1, "Gordon", "D03"), 153 | new BoardingPass("FOO-100", 2, "Johnson", "C02") 154 | ); 155 | } 156 | 157 | @Test 158 | public void shouldReturnLastPageOfSortedResults() throws Exception { 159 | //given 160 | repository.save(new BoardingPass("FOO-100", 1, "Smith", "B01")); 161 | repository.save(new BoardingPass("FOO-100", 2, "Johnson", "C02")); 162 | repository.save(new BoardingPass("BAR-100", 2, "Who", "E04")); 163 | repository.save(new BoardingPass("BAR-100", 1, "Gordon", "D03")); 164 | 165 | //when 166 | final Page page = repository.findAll( 167 | new PageRequest(1, 3, 168 | new Sort( 169 | new Order(ASC, "flight_no"), 170 | new Order(DESC, "seq_no") 171 | ) 172 | )); 173 | 174 | //then 175 | assertThat(page.getContent()).containsExactly(new BoardingPass("FOO-100", 1, "Smith", "B01")); 176 | } 177 | 178 | @Test 179 | public void shouldReturnNothingWhenFindingByListOfIdsButListEmpty() throws Exception { 180 | //given 181 | repository.save(new BoardingPass("FOO-100", 1, "Smith", "B01")); 182 | repository.save(new BoardingPass("FOO-100", 2, "Smith", "B01")); 183 | repository.save(new BoardingPass("FOO-100", 3, "Smith", "B01")); 184 | 185 | //when 186 | final Iterable none = repository.findAll(Collections.emptyList()); 187 | 188 | //then 189 | assertThat(none).isEmpty(); 190 | } 191 | 192 | @Test 193 | public void shouldSelectOneRecordById() throws Exception { 194 | //given 195 | repository.save(new BoardingPass("FOO-100", 1, "Smith", "B01")); 196 | final Object[] idOfSecond = repository.save(new BoardingPass("FOO-100", 2, "Smith", "B01")).getId(); 197 | repository.save(new BoardingPass("FOO-100", 3, "Smith", "B01")); 198 | 199 | //when 200 | final List oneRecord = Lists.newArrayList(repository.findAll(Arrays.asList(idOfSecond))); 201 | 202 | //then 203 | assertThat(oneRecord).hasSize(1); 204 | assertThat(oneRecord.get(0).getId()).isEqualTo(idOfSecond); 205 | } 206 | 207 | @Test 208 | public void shouldSelectMultipleRecordsById() throws Exception { 209 | //given 210 | final Object[] idOfFirst = repository.save(new BoardingPass("FOO-100", 1, "Smith", "B01")).getId(); 211 | repository.save(new BoardingPass("FOO-100", 2, "Smith", "B01")); 212 | final Object[] idOfThird = repository.save(new BoardingPass("FOO-100", 3, "Smith", "B01")).getId(); 213 | 214 | //when 215 | final List boardingPasses = Lists.newArrayList(repository.findAll(Arrays.asList(idOfFirst, idOfThird))); 216 | 217 | //then 218 | assertThat(boardingPasses).hasSize(2); 219 | Collections.sort(boardingPasses, new Comparator() { 220 | @Override 221 | public int compare(BoardingPass o1, BoardingPass o2) { 222 | return o1.getSeqNo() - o2.getSeqNo(); 223 | } 224 | }); 225 | assertThat(boardingPasses.get(0).getId()).isEqualTo(idOfFirst); 226 | assertThat(boardingPasses.get(1).getId()).isEqualTo(idOfThird); 227 | } 228 | 229 | 230 | } 231 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | org.sonatype.oss 5 | oss-parent 6 | 7 7 | 8 | com.nurkiewicz.jdbcrepository 9 | jdbcrepository 10 | 0.4.2-SNAPSHOT 11 | jar 12 | Spring Data JDBC repository 13 | A repository implementation compatible with Spring Data abstraction that uses raw repositories 14 | https://github.com/nurkiewicz/spring-data-jdbc-repository 15 | 16 | 17 | Tomasz Nurkiewicz 18 | http://nurkiewicz.com 19 | 20 | 21 | Sheena Artrip 22 | 23 | 24 | Thomas Darimont 25 | 26 | 27 | 28 | 29 | Apache License, Version 2.0 30 | http://www.apache.org/licenses/LICENSE-2.0 31 | 32 | 33 | 34 | 35 | UTF-8 36 | 4.1.1.RELEASE 37 | 38 | 39 | 40 | scm:git:git@github.com:nurkiewicz/spring-data-jdbc-repository.git 41 | scm:git:git@github.com:nurkiewicz/spring-data-jdbc-repository.git 42 | scm:git:git@github.com:nurkiewicz/spring-data-jdbc-repository.git 43 | HEAD 44 | 45 | 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-compiler-plugin 51 | 3.0 52 | 53 | 1.6 54 | 1.6 55 | 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-release-plugin 60 | 2.5 61 | 62 | @{project.version} 63 | 64 | 65 | 66 | maven-source-plugin 67 | 2.2.1 68 | 69 | 70 | attach-sources 71 | 72 | jar 73 | 74 | 75 | 76 | 77 | 78 | maven-javadoc-plugin 79 | 2.9.1 80 | 81 | 82 | attach-javadocs 83 | 84 | jar 85 | 86 | 87 | 88 | 89 | 90 | maven-gpg-plugin 91 | 1.5 92 | 93 | 94 | sign-artifacts 95 | verify 96 | 97 | sign 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-checkstyle-plugin 105 | 2.9.1 106 | 107 | checkstyle.xml 108 | true 109 | true 110 | true 111 | 112 | 113 | 114 | 115 | check-style 116 | compile 117 | 118 | checkstyle 119 | 120 | 121 | 122 | test-check-style 123 | test-compile 124 | 125 | checkstyle 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | oracle 136 | 144 | 145 | 146 | com.oracle 147 | ojdbc6 148 | 11.2.0.3 149 | test 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | org.springframework.data 158 | spring-data-commons 159 | 1.9.0.RELEASE 160 | 161 | 162 | org.springframework 163 | spring-jdbc 164 | ${spring.version} 165 | 166 | 167 | org.springframework 168 | spring-tx 169 | ${spring.version} 170 | 171 | 172 | org.springframework 173 | spring-beans 174 | ${spring.version} 175 | 176 | 177 | org.springframework 178 | spring-core 179 | ${spring.version} 180 | 181 | 182 | 183 | 184 | junit 185 | junit 186 | 4.11 187 | test 188 | 189 | 190 | org.springframework 191 | spring-test 192 | ${spring.version} 193 | test 194 | 195 | 196 | org.springframework 197 | spring-aop 198 | ${spring.version} 199 | test 200 | 201 | 202 | org.springframework 203 | spring-context 204 | ${spring.version} 205 | test 206 | 207 | 208 | cglib 209 | cglib-nodep 210 | 3.1 211 | test 212 | 213 | 214 | org.easytesting 215 | fest-assert-core 216 | 2.0M10 217 | test 218 | 219 | 220 | com.google.guava 221 | guava 222 | 18.0 223 | test 224 | 225 | 226 | 227 | 228 | commons-logging 229 | commons-logging 230 | 1.1.3 231 | provided 232 | 233 | 234 | org.slf4j 235 | jcl-over-slf4j 236 | 1.7.7 237 | test 238 | 239 | 240 | ch.qos.logback 241 | logback-classic 242 | 1.1.2 243 | test 244 | 245 | 246 | 247 | 248 | com.h2database 249 | h2 250 | 1.4.181 251 | test 252 | 253 | 254 | mysql 255 | mysql-connector-java 256 | 5.1.33 257 | test 258 | 259 | 260 | postgresql 261 | postgresql 262 | 9.1-901-1.jdbc4 263 | test 264 | 265 | 266 | org.hsqldb 267 | hsqldb 268 | 2.3.2 269 | test 270 | 271 | 272 | org.apache.derby 273 | derby 274 | 10.11.1.1 275 | test 276 | 277 | 278 | net.sourceforge.jtds 279 | jtds 280 | 1.2.7 281 | test 282 | 283 | 284 | com.jolbox 285 | bonecp 286 | 0.8.0.RELEASE 287 | test 288 | 289 | 290 | 291 | 292 | -------------------------------------------------------------------------------- /src/main/java/com/nurkiewicz/jdbcrepository/JdbcRepository.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import com.nurkiewicz.jdbcrepository.sql.SqlGenerator; 4 | import org.springframework.beans.BeansException; 5 | import org.springframework.beans.factory.BeanFactory; 6 | import org.springframework.beans.factory.BeanFactoryAware; 7 | import org.springframework.beans.factory.InitializingBean; 8 | import org.springframework.beans.factory.NoSuchBeanDefinitionException; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.PageImpl; 11 | import org.springframework.data.domain.Pageable; 12 | import org.springframework.data.domain.Persistable; 13 | import org.springframework.data.domain.Sort; 14 | import org.springframework.data.repository.PagingAndSortingRepository; 15 | import org.springframework.jdbc.core.JdbcOperations; 16 | import org.springframework.jdbc.core.JdbcTemplate; 17 | import org.springframework.jdbc.core.PreparedStatementCreator; 18 | import org.springframework.jdbc.core.RowMapper; 19 | import org.springframework.jdbc.support.GeneratedKeyHolder; 20 | import org.springframework.util.Assert; 21 | 22 | import javax.sql.DataSource; 23 | import java.io.Serializable; 24 | import java.sql.Connection; 25 | import java.sql.PreparedStatement; 26 | import java.sql.SQLException; 27 | import java.util.ArrayList; 28 | import java.util.Arrays; 29 | import java.util.Collections; 30 | import java.util.LinkedHashMap; 31 | import java.util.List; 32 | import java.util.Map; 33 | 34 | /** 35 | * Implementation of {@link PagingAndSortingRepository} using {@link JdbcTemplate} 36 | */ 37 | public abstract class JdbcRepository, ID extends Serializable> implements PagingAndSortingRepository, InitializingBean, BeanFactoryAware { 38 | 39 | public static Object[] pk(Object... idValues) { 40 | return idValues; 41 | } 42 | 43 | private final TableDescription table; 44 | 45 | private final RowMapper rowMapper; 46 | private final RowUnmapper rowUnmapper; 47 | 48 | private SqlGenerator sqlGenerator = new SqlGenerator(); 49 | private BeanFactory beanFactory; 50 | private JdbcOperations jdbcOperations; 51 | 52 | public JdbcRepository(RowMapper rowMapper, RowUnmapper rowUnmapper, SqlGenerator sqlGenerator, TableDescription table) { 53 | Assert.notNull(rowMapper); 54 | Assert.notNull(rowUnmapper); 55 | Assert.notNull(table); 56 | 57 | this.rowUnmapper = rowUnmapper; 58 | this.rowMapper = rowMapper; 59 | this.sqlGenerator = sqlGenerator; 60 | this.table = table; 61 | } 62 | 63 | public JdbcRepository(RowMapper rowMapper, RowUnmapper rowUnmapper, TableDescription table) { 64 | this(rowMapper, rowUnmapper, null, table); 65 | } 66 | 67 | public JdbcRepository(RowMapper rowMapper, RowUnmapper rowUnmapper, String tableName, String idColumn) { 68 | this(rowMapper, rowUnmapper, null, new TableDescription(tableName, idColumn)); 69 | } 70 | 71 | public JdbcRepository(RowMapper rowMapper, RowUnmapper rowUnmapper, String tableName) { 72 | this(rowMapper, rowUnmapper, new TableDescription(tableName, "id")); 73 | } 74 | 75 | public JdbcRepository(RowMapper rowMapper, TableDescription table) { 76 | this(rowMapper, new MissingRowUnmapper(), null, table); 77 | } 78 | 79 | public JdbcRepository(RowMapper rowMapper, String tableName, String idColumn) { 80 | this(rowMapper, new MissingRowUnmapper(), null, new TableDescription(tableName, idColumn)); 81 | } 82 | 83 | public JdbcRepository(RowMapper rowMapper, String tableName) { 84 | this(rowMapper, new MissingRowUnmapper(), new TableDescription(tableName, "id")); 85 | } 86 | 87 | @Override 88 | public void afterPropertiesSet() throws Exception { 89 | obtainJdbcTemplate(); 90 | if (sqlGenerator == null) { 91 | obtainSqlGenerator(); 92 | } 93 | } 94 | 95 | public void setSqlGenerator(SqlGenerator sqlGenerator) { 96 | this.sqlGenerator = sqlGenerator; 97 | } 98 | 99 | public void setJdbcOperations(JdbcOperations jdbcOperations) { 100 | this.jdbcOperations = jdbcOperations; 101 | } 102 | 103 | public void setDataSource(DataSource dataSource) { 104 | this.jdbcOperations = new JdbcTemplate(dataSource); 105 | } 106 | 107 | protected TableDescription getTable() { 108 | return table; 109 | } 110 | 111 | private void obtainSqlGenerator() { 112 | try { 113 | sqlGenerator = beanFactory.getBean(SqlGenerator.class); 114 | } catch (NoSuchBeanDefinitionException e) { 115 | sqlGenerator = new SqlGenerator(); 116 | } 117 | } 118 | 119 | private void obtainJdbcTemplate() { 120 | try { 121 | jdbcOperations = beanFactory.getBean(JdbcOperations.class); 122 | } catch (NoSuchBeanDefinitionException e) { 123 | final DataSource dataSource = beanFactory.getBean(DataSource.class); 124 | jdbcOperations = new JdbcTemplate(dataSource); 125 | } 126 | } 127 | 128 | @Override 129 | public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 130 | this.beanFactory = beanFactory; 131 | } 132 | 133 | @Override 134 | public long count() { 135 | return jdbcOperations.queryForObject(sqlGenerator.count(table), Long.class); 136 | } 137 | 138 | @Override 139 | public void delete(ID id) { 140 | jdbcOperations.update(sqlGenerator.deleteById(table), idToObjectArray(id)); 141 | } 142 | 143 | @Override 144 | public void delete(T entity) { 145 | jdbcOperations.update(sqlGenerator.deleteById(table), idToObjectArray(entity.getId())); 146 | } 147 | 148 | @Override 149 | public void delete(Iterable entities) { 150 | for (T t : entities) { 151 | delete(t); 152 | } 153 | } 154 | 155 | @Override 156 | public void deleteAll() { 157 | jdbcOperations.update(sqlGenerator.deleteAll(table)); 158 | } 159 | 160 | @Override 161 | public boolean exists(ID id) { 162 | return jdbcOperations.queryForObject(sqlGenerator.countById(table), Integer.class, idToObjectArray(id)) > 0; 163 | } 164 | 165 | @Override 166 | public List findAll() { 167 | return jdbcOperations.query(sqlGenerator.selectAll(table), rowMapper); 168 | } 169 | 170 | @Override 171 | public T findOne(ID id) { 172 | final Object[] idColumns = idToObjectArray(id); 173 | final List entityOrEmpty = jdbcOperations.query(sqlGenerator.selectById(table), idColumns, rowMapper); 174 | return entityOrEmpty.isEmpty() ? null : entityOrEmpty.get(0); 175 | } 176 | 177 | private static Object[] idToObjectArray(ID id) { 178 | if (id instanceof Object[]) 179 | return (Object[]) id; 180 | else 181 | return new Object[]{id}; 182 | } 183 | 184 | private static List idToObjectList(ID id) { 185 | if (id instanceof Object[]) 186 | return Arrays.asList((Object[]) id); 187 | else 188 | return Collections.singletonList(id); 189 | } 190 | 191 | @Override 192 | public S save(S entity) { 193 | if (entity.isNew()) { 194 | return create(entity); 195 | } else { 196 | return update(entity); 197 | } 198 | } 199 | 200 | protected S update(S entity) { 201 | final Map columns = preUpdate(entity, columns(entity)); 202 | final List idValues = removeIdColumns(columns); 203 | final String updateQuery = sqlGenerator.update(table, columns); 204 | for (int i = 0; i < table.getIdColumns().size(); ++i) { 205 | columns.put(table.getIdColumns().get(i), idValues.get(i)); 206 | } 207 | final Object[] queryParams = columns.values().toArray(); 208 | jdbcOperations.update(updateQuery, queryParams); 209 | return postUpdate(entity); 210 | } 211 | 212 | protected Map preUpdate(T entity, Map columns) { 213 | return columns; 214 | } 215 | 216 | protected S create(S entity) { 217 | final Map columns = preCreate(columns(entity), entity); 218 | if (entity.getId() == null) { 219 | return createWithAutoGeneratedKey(entity, columns); 220 | } else { 221 | return createWithManuallyAssignedKey(entity, columns); 222 | } 223 | } 224 | 225 | private S createWithManuallyAssignedKey(S entity, Map columns) { 226 | final String createQuery = sqlGenerator.create(table, columns); 227 | final Object[] queryParams = columns.values().toArray(); 228 | jdbcOperations.update(createQuery, queryParams); 229 | return postCreate(entity, null); 230 | } 231 | 232 | private S createWithAutoGeneratedKey(S entity, Map columns) { 233 | removeIdColumns(columns); 234 | final String createQuery = sqlGenerator.create(table, columns); 235 | final Object[] queryParams = columns.values().toArray(); 236 | final GeneratedKeyHolder key = new GeneratedKeyHolder(); 237 | jdbcOperations.update(new PreparedStatementCreator() { 238 | @Override 239 | public PreparedStatement createPreparedStatement(Connection con) throws SQLException { 240 | final String idColumnName = table.getIdColumns().get(0); 241 | final PreparedStatement ps = con.prepareStatement(createQuery, new String[]{idColumnName}); 242 | for (int i = 0; i < queryParams.length; ++i) { 243 | ps.setObject(i + 1, queryParams[i]); 244 | } 245 | return ps; 246 | } 247 | }, key); 248 | return postCreate(entity, key.getKey()); 249 | } 250 | 251 | private List removeIdColumns(Map columns) { 252 | List idColumnsValues = new ArrayList(columns.size()); 253 | for (String idColumn : table.getIdColumns()) { 254 | idColumnsValues.add(columns.remove(idColumn)); 255 | } 256 | return idColumnsValues; 257 | } 258 | 259 | protected Map preCreate(Map columns, T entity) { 260 | return columns; 261 | } 262 | 263 | private LinkedHashMap columns(T entity) { 264 | return new LinkedHashMap(rowUnmapper.mapColumns(entity)); 265 | } 266 | 267 | protected S postUpdate(S entity) { 268 | return entity; 269 | } 270 | 271 | /** 272 | * General purpose hook method that is called every time {@link #create} is called with a new entity. 273 | *

274 | * OVerride this method e.g. if you want to fetch auto-generated key from database 275 | * 276 | * 277 | * @param entity Entity that was passed to {@link #create} 278 | * @param generatedId ID generated during INSERT or NULL if not available/not generated. 279 | * todo: Type should be ID, not Number 280 | * @return Either the same object as an argument or completely different one 281 | */ 282 | protected S postCreate(S entity, Number generatedId) { 283 | return entity; 284 | } 285 | 286 | @Override 287 | public Iterable save(Iterable entities) { 288 | List ret = new ArrayList(); 289 | for (S s : entities) { 290 | ret.add(save(s)); 291 | } 292 | return ret; 293 | } 294 | 295 | @Override 296 | public Iterable findAll(Iterable ids) { 297 | final List idsList = toList(ids); 298 | if (idsList.isEmpty()) { 299 | return Collections.emptyList(); 300 | } 301 | final Object[] idColumnValues = flatten(idsList); 302 | return jdbcOperations.query(sqlGenerator.selectByIds(table, idsList.size()), rowMapper, idColumnValues); 303 | } 304 | 305 | private static List toList(Iterable iterable) { 306 | final List result = new ArrayList(); 307 | for (T item : iterable) { 308 | result.add(item); 309 | } 310 | return result; 311 | } 312 | 313 | private static Object[] flatten(List ids) { 314 | final List result = new ArrayList(); 315 | for (ID id : ids) { 316 | result.addAll(idToObjectList(id)); 317 | } 318 | return result.toArray(); 319 | } 320 | 321 | @Override 322 | public List findAll(Sort sort) { 323 | return jdbcOperations.query(sqlGenerator.selectAll(table, sort), rowMapper); 324 | } 325 | 326 | @Override 327 | public Page findAll(Pageable page) { 328 | String query = sqlGenerator.selectAll(table, page); 329 | return new PageImpl(jdbcOperations.query(query, rowMapper), page, count()); 330 | } 331 | 332 | } 333 | 334 | 335 | -------------------------------------------------------------------------------- /src/test/java/com/nurkiewicz/jdbcrepository/JdbcRepositoryManualKeyTest.java: -------------------------------------------------------------------------------- 1 | package com.nurkiewicz.jdbcrepository; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.nurkiewicz.jdbcrepository.repositories.CommentRepository; 5 | import com.nurkiewicz.jdbcrepository.repositories.User; 6 | import com.nurkiewicz.jdbcrepository.repositories.UserRepository; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.PageRequest; 11 | import org.springframework.data.domain.Sort; 12 | import org.springframework.jdbc.core.JdbcOperations; 13 | import org.springframework.jdbc.core.JdbcTemplate; 14 | 15 | import javax.annotation.Resource; 16 | import javax.sql.DataSource; 17 | import java.sql.Date; 18 | import java.util.Arrays; 19 | import java.util.Calendar; 20 | import java.util.Collections; 21 | import java.util.Comparator; 22 | import java.util.GregorianCalendar; 23 | import java.util.List; 24 | 25 | import static org.fest.assertions.api.Assertions.assertThat; 26 | import static org.springframework.data.domain.Sort.Direction.ASC; 27 | import static org.springframework.data.domain.Sort.Direction.DESC; 28 | import static org.springframework.data.domain.Sort.Order; 29 | 30 | public abstract class JdbcRepositoryManualKeyTest extends AbstractIntegrationTest { 31 | 32 | public static final int SOME_REPUTATION = 42; 33 | 34 | @Resource 35 | private UserRepository repository; 36 | 37 | @Resource 38 | private CommentRepository commentRepository; 39 | 40 | @Resource 41 | private DataSource dataSource; 42 | 43 | private JdbcOperations jdbc; 44 | 45 | private static final Date SOME_DATE_OF_BIRTH = new Date(new GregorianCalendar(2013, Calendar.JANUARY, 9).getTimeInMillis()); 46 | 47 | public JdbcRepositoryManualKeyTest() { 48 | } 49 | 50 | public JdbcRepositoryManualKeyTest(int databasePort) { 51 | super(databasePort); 52 | } 53 | 54 | @Before 55 | public void setup() { 56 | jdbc = new JdbcTemplate(dataSource); 57 | } 58 | 59 | 60 | @Test 61 | public void shouldReturnNullWhenDatabaseEmptyAndSearchingById() { 62 | //given 63 | String notExistingId = "Foo"; 64 | 65 | //when 66 | User user = repository.findOne(notExistingId); 67 | 68 | //then 69 | assertThat(user).isNull(); 70 | } 71 | 72 | @Test 73 | public void shouldReturnEmptyListWhenDatabaseEmpty() { 74 | //given 75 | 76 | //when 77 | Iterable all = repository.findAll(); 78 | 79 | //then 80 | assertThat(all).isEmpty(); 81 | } 82 | 83 | @Test 84 | public void shouldReturnEmptyPageWhenNoEntitiesInDatabase() { 85 | //given 86 | 87 | //when 88 | Page firstPage = repository.findAll(new PageRequest(0, 20)); 89 | 90 | //then 91 | assertThat(firstPage).isEmpty(); 92 | assertThat(firstPage.getTotalElements()).isZero(); 93 | assertThat(firstPage.getSize()).isEqualTo(20); 94 | assertThat(firstPage.getNumber()).isZero(); 95 | } 96 | 97 | private User user(String userName) { 98 | return new User(userName, SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 99 | } 100 | 101 | @Test 102 | public void shouldSaveOneRecord() { 103 | //given 104 | User john = user("john"); 105 | 106 | //when 107 | repository.save(john); 108 | Iterable all = repository.findAll(); 109 | 110 | //then 111 | assertThat(all).hasSize(1); 112 | User record = all.iterator().next(); 113 | assertThat(record).isEqualTo(user("john")); 114 | } 115 | 116 | @Test 117 | public void shouldUpdatePreviouslySavedRecord() throws Exception { 118 | //given 119 | User john = repository.save(user("john")); 120 | john.setEnabled(false); 121 | john.setReputation(45); 122 | 123 | //when 124 | repository.save(john); 125 | 126 | //then 127 | User updated = repository.findOne("john"); 128 | assertThat(updated).isEqualTo(new User("john", SOME_DATE_OF_BIRTH, 45, false)); 129 | } 130 | 131 | @Test 132 | public void shouldReturnOneRecordById() { 133 | //given 134 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "james", SOME_DATE_OF_BIRTH, 43, false); 135 | 136 | //when 137 | User user = repository.findOne("james"); 138 | 139 | //then 140 | assertThat(user).isEqualTo(new User("james", SOME_DATE_OF_BIRTH, 43, false)); 141 | } 142 | 143 | @Test 144 | public void shouldReturnNullWhenEntityForGivenIdDoesNotExist() throws Exception { 145 | //given 146 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "james", SOME_DATE_OF_BIRTH, 43, false); 147 | 148 | //when 149 | User user = repository.findOne("john"); 150 | 151 | //then 152 | assertThat(user).isNull(); 153 | } 154 | 155 | @Test 156 | public void shouldReturnListWithOneItemWhenOneRecordInDatabase() { 157 | //given 158 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john2", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 159 | 160 | //when 161 | Iterable all = repository.findAll(); 162 | 163 | //then 164 | assertThat(all).hasSize(1); 165 | User record = all.iterator().next(); 166 | assertThat(record.getId()).isEqualTo("john2"); 167 | } 168 | 169 | @Test 170 | public void shouldReturnPageWithOneItemWhenOneRecordInDatabase() { 171 | //given 172 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john4", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 173 | 174 | //when 175 | Page page = repository.findAll(new PageRequest(0, 5)); 176 | 177 | //then 178 | assertThat(page).hasSize(1); 179 | assertThat(page.getTotalElements()).isEqualTo(1); 180 | assertThat(page.getSize()).isEqualTo(5); 181 | assertThat(page.getNumber()).isZero(); 182 | assertThat(page.getContent().get(0).getId()).isEqualTo("john4"); 183 | } 184 | 185 | @Test 186 | public void shouldReturnNothingWhenOnlyOneRecordInDatabaseButSecondPageRequested() { 187 | //given 188 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john5", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 189 | 190 | //when 191 | Page page = repository.findAll(new PageRequest(1, 5)); 192 | 193 | //then 194 | assertThat(page).hasSize(0); 195 | assertThat(page.getTotalElements()).isEqualTo(1); 196 | assertThat(page.getSize()).isEqualTo(5); 197 | assertThat(page.getNumber()).isEqualTo(1); 198 | } 199 | 200 | @Test 201 | public void shouldReturnPageWithOneItemWithSortingApplied() { 202 | //given 203 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john6", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 204 | 205 | //when 206 | Page page = repository.findAll(new PageRequest(0, 5, ASC, "user_name")); 207 | 208 | //then 209 | assertThat(page).hasSize(1); 210 | assertThat(page.getTotalElements()).isEqualTo(1); 211 | assertThat(page.getSize()).isEqualTo(5); 212 | assertThat(page.getNumber()).isZero(); 213 | assertThat(page.getContent().get(0).getId()).isEqualTo("john6"); 214 | } 215 | 216 | @Test 217 | public void shouldReturnPageWithOneItemWithSortingAppliedOnTwoProperties() { 218 | //given 219 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john6", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 220 | 221 | //when 222 | Page page = repository.findAll(new PageRequest(0, 5, new Sort(new Order(DESC, "reputation"), new Order(ASC, "user_name")))); 223 | 224 | //then 225 | assertThat(page).hasSize(1); 226 | assertThat(page.getTotalElements()).isEqualTo(1); 227 | assertThat(page.getSize()).isEqualTo(5); 228 | assertThat(page.getNumber()).isZero(); 229 | assertThat(page.getContent().get(0).getId()).isEqualTo("john6"); 230 | } 231 | 232 | @Test 233 | public void shouldReturnFirstPageSortedByReputation() { 234 | //given 235 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john11", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 2, true); 236 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john12", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 1, true); 237 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john13", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 2, true); 238 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john14", SOME_DATE_OF_BIRTH, SOME_REPUTATION , true); 239 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john15", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 1, true); 240 | 241 | //when 242 | Page page = repository.findAll(new PageRequest(0, 3, new Sort(new Order(DESC, "reputation"), new Order(ASC, "user_name")))); 243 | 244 | //then 245 | assertThat(page).hasSize(3); 246 | assertThat(page.getTotalElements()).isEqualTo(5); 247 | assertThat(page.getSize()).isEqualTo(3); 248 | assertThat(page.getNumber()).isZero(); 249 | assertThat(page.getContent()).containsExactly( 250 | new User("john11", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 2, true), 251 | new User("john13", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 2, true), 252 | new User("john12", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 1, true) 253 | ); 254 | } 255 | 256 | @Test 257 | public void shouldReturnSecondPageSortedByReputation() { 258 | //given 259 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john11", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 2, true); 260 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john12", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 1, true); 261 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john13", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 2, true); 262 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john14", SOME_DATE_OF_BIRTH, SOME_REPUTATION , true); 263 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john15", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 1, true); 264 | 265 | //when 266 | Page page = repository.findAll(new PageRequest(1, 3, new Sort(new Order(DESC, "reputation"), new Order(ASC, "user_name")))); 267 | 268 | //then 269 | assertThat(page).hasSize(2); 270 | assertThat(page.getTotalElements()).isEqualTo(5); 271 | assertThat(page.getSize()).isEqualTo(3); 272 | assertThat(page.getNumber()).isEqualTo(1); 273 | assertThat(page.getContent()).containsExactly( 274 | new User("john15", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 1, true), 275 | new User("john14", SOME_DATE_OF_BIRTH, SOME_REPUTATION , true) 276 | ); 277 | } 278 | 279 | @Test 280 | public void shouldReturnEmptyListWhenFindAllCalledWithoutPaging() throws Exception { 281 | //given 282 | final Sort sort = new Sort("reputation"); 283 | 284 | //when 285 | final Iterable reputation = repository.findAll(sort); 286 | 287 | //then 288 | assertThat(reputation).isEmpty(); 289 | } 290 | 291 | @Test 292 | public void shouldReturnEmptyListWhenFindAllCalledWithoutPagingButWithSortingOnMultipleProperties() throws Exception { 293 | //given 294 | final Sort sort = new Sort(new Order(DESC, "reputation"), new Order(ASC, "date_of_birth")); 295 | 296 | //when 297 | final Iterable reputation = repository.findAll(sort); 298 | 299 | //then 300 | assertThat(reputation).isEmpty(); 301 | } 302 | 303 | @Test 304 | public void shouldReturnSingleRecordWhenFindAllWithoutPagingButWithSorting() throws Exception { 305 | //given 306 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john7", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 307 | final Sort sort = new Sort(new Order(DESC, "reputation"), new Order(ASC, "date_of_birth")); 308 | 309 | //when 310 | final Iterable all = repository.findAll(sort); 311 | 312 | //then 313 | assertThat(all).hasSize(1); 314 | assertThat(all.iterator().next()).isEqualTo(new User("john7", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true)); 315 | } 316 | 317 | @Test 318 | public void shouldSortMultipleRecordsByTwoDifferentOrderingProperties() throws Exception { 319 | //given 320 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john3", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 321 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john5", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 1, true); 322 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john4", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 1, true); 323 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john6", SOME_DATE_OF_BIRTH, SOME_REPUTATION - 1, true); 324 | final Sort sort = new Sort(new Order(DESC, "reputation"), new Order(ASC, "user_name")); 325 | 326 | //when 327 | final List all = repository.findAll(sort); 328 | 329 | //then 330 | assertThat(all).containsExactly( 331 | new User("john4", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 1, true), 332 | new User("john5", SOME_DATE_OF_BIRTH, SOME_REPUTATION + 1, true), 333 | new User("john3", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true), 334 | new User("john6", SOME_DATE_OF_BIRTH, SOME_REPUTATION - 1, true) 335 | ); 336 | } 337 | 338 | @Test 339 | public void shouldReturnFalseWhenExistsCalledOnEmptyDatabase() { 340 | //given 341 | 342 | //when 343 | boolean exists = repository.exists("john"); 344 | 345 | //then 346 | assertThat(exists).isFalse(); 347 | } 348 | 349 | @Test 350 | public void shouldReturnFalseWhenEntityWithSuchIdDoesNotExist() { 351 | //given 352 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john7", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 353 | 354 | //when 355 | boolean exists = repository.exists("john6"); 356 | 357 | //then 358 | assertThat(exists).isFalse(); 359 | } 360 | 361 | @Test 362 | public void shouldReturnTrueWhenEntityForGivenIdExists() { 363 | //given 364 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", "john8", SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 365 | 366 | //when 367 | boolean exists = repository.exists("john8"); 368 | 369 | //then 370 | assertThat(exists).isTrue(); 371 | } 372 | 373 | @Test 374 | public void shouldDeleteEntityById() throws Exception { 375 | //given 376 | final String someId = "john9"; 377 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", someId, SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 378 | 379 | //when 380 | repository.delete(someId); 381 | 382 | //then 383 | assertThat(jdbc.queryForObject("SELECT COUNT(user_name) FROM USERS WHERE user_name = ?", Integer.class, someId)).isZero(); 384 | } 385 | 386 | @Test 387 | public void shouldDoNothingWhenEntityForGivenIdDoesNotExist() throws Exception { 388 | //given 389 | final String someId = "john10"; 390 | 391 | //when 392 | repository.delete(someId); 393 | 394 | //then 395 | //no exception 396 | } 397 | 398 | @Test 399 | public void shouldNotDeleteEntityWithDifferentId() throws Exception { 400 | //given 401 | final String someId = "john11"; 402 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", someId, SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 403 | 404 | //when 405 | repository.delete(someId + "_"); 406 | 407 | //then 408 | assertThat(jdbc.queryForList("SELECT user_name FROM USERS WHERE user_name = ?", String.class, someId)).containsExactly(someId); 409 | } 410 | 411 | @Test 412 | public void shouldDeleteByEntity() throws Exception { 413 | //given 414 | final String someId = "john12"; 415 | final User user = repository.save(user(someId)); 416 | 417 | //when 418 | repository.delete(user); 419 | 420 | //then 421 | assertThat(jdbc.queryForObject("SELECT COUNT(user_name) FROM USERS WHERE user_name = ?", 422 | Integer.class, someId)).isZero(); 423 | } 424 | 425 | @Test 426 | public void shouldReturnZeroForCountWhenEmptyTable() throws Exception { 427 | //given 428 | 429 | //when 430 | final long count = repository.count(); 431 | 432 | //then 433 | assertThat(count).isZero(); 434 | } 435 | 436 | @Test 437 | public void shouldReturnOneWhenSingleElementInTable() throws Exception { 438 | //given 439 | final String someId = "john12"; 440 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", someId, SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 441 | 442 | //when 443 | final long count = repository.count(); 444 | 445 | //then 446 | assertThat(count).isEqualTo(1); 447 | } 448 | 449 | @Test 450 | public void shouldReturnCountOfRecordsInTable() throws Exception { 451 | //given 452 | insertRecordsForIds("1", "2", "3"); 453 | 454 | //when 455 | final long count = repository.count(); 456 | 457 | //then 458 | assertThat(count).isEqualTo(3); 459 | } 460 | 461 | @Test 462 | public void shouldSaveMultipleEntities() throws Exception { 463 | //given 464 | User john = user("john"); 465 | User alice = user("alice"); 466 | 467 | //when 468 | repository.save(Arrays.asList(john, alice)); 469 | 470 | //then 471 | assertThat(jdbc.queryForList("SELECT user_name FROM USERS ORDER BY user_name", String.class)).containsExactly("alice", "john"); 472 | } 473 | 474 | @Test 475 | public void shouldDeleteMultipleEntities() throws Exception { 476 | //given 477 | User john = user("john"); 478 | User alice = user("alice"); 479 | repository.save(Arrays.asList(john, alice)); 480 | 481 | //when 482 | repository.delete(Arrays.asList(john, alice)); 483 | 484 | //then 485 | assertThat(jdbc.queryForObject("SELECT COUNT(user_name) FROM USERS", Integer.class)).isZero(); 486 | } 487 | 488 | @Test 489 | public void shouldSkipNonListedEntitiesWhenDeletingInBatch() throws Exception { 490 | //given 491 | User john = user("john"); 492 | User alice = user("alice"); 493 | final User bobby = user("bobby"); 494 | repository.save(Arrays.asList(john, alice, bobby)); 495 | 496 | //when 497 | repository.delete(Arrays.asList(john, alice)); 498 | 499 | //then 500 | assertThat(jdbc.queryForList("SELECT user_name FROM USERS", String.class)).containsExactly("bobby"); 501 | } 502 | 503 | @Test 504 | public void shouldSkipNonExistingEntitiesWhenDeletingInBatch() throws Exception { 505 | //given 506 | User john = user("john"); 507 | User alice = user("alice"); 508 | final User bobby = user("bobby"); 509 | repository.save(Arrays.asList(john, alice, bobby)); 510 | 511 | //when 512 | repository.delete(Arrays.asList(john, alice, user("bogus"))); 513 | 514 | //then 515 | assertThat(jdbc.queryForList("SELECT user_name FROM USERS", String.class)).containsExactly("bobby"); 516 | } 517 | 518 | @Test 519 | public void shouldDoNothingWhenDeletingAllButEmptyTable() throws Exception { 520 | //given 521 | 522 | //when 523 | repository.deleteAll(); 524 | 525 | //then 526 | assertThat(jdbc.queryForObject("SELECT COUNT(user_name) FROM USERS", Integer.class)).isZero(); 527 | } 528 | 529 | @Test 530 | public void shouldDeleteAllRecordsInTable() throws Exception { 531 | //given 532 | insertRecordsForIds("1", "2", "3"); 533 | 534 | //when 535 | repository.deleteAll(); 536 | 537 | //then 538 | assertThat(jdbc.queryForObject("SELECT COUNT(user_name) FROM USERS", Integer.class)).isZero(); 539 | } 540 | 541 | private void insertRecordsForIds(String... ids) { 542 | for(String id: ids) { 543 | jdbc.update("INSERT INTO USERS VALUES (?, ?, ?, ?)", id, SOME_DATE_OF_BIRTH, SOME_REPUTATION, true); 544 | } 545 | } 546 | 547 | @Test 548 | public void shouldReturnNothingWhenFindingByListOfIdsButListEmpty() throws Exception { 549 | //given 550 | insertRecordsForIds("1", "2", "3"); 551 | 552 | //when 553 | final Iterable none = repository.findAll(Collections.emptyList()); 554 | 555 | //then 556 | assertThat(none).isEmpty(); 557 | } 558 | 559 | @Test 560 | public void shouldSelectOneRecordById() throws Exception { 561 | //given 562 | insertRecordsForIds("1", "2", "3"); 563 | 564 | //when 565 | final List oneRecord = Lists.newArrayList(repository.findAll(Arrays.asList("2"))); 566 | 567 | //then 568 | assertThat(oneRecord).hasSize(1); 569 | assertThat(oneRecord.get(0).getId()).isEqualTo("2"); 570 | } 571 | 572 | @Test 573 | public void shouldSelectMultipleRecordsById() throws Exception { 574 | //given 575 | insertRecordsForIds("1", "2", "3"); 576 | 577 | //when 578 | final List users = Lists.newArrayList(repository.findAll(Arrays.asList("1", "3"))); 579 | 580 | //then 581 | assertThat(users).hasSize(2); 582 | Collections.sort(users, new Comparator() { 583 | @Override 584 | public int compare(User o1, User o2) { 585 | return o1.getId().compareTo(o2.getId()); 586 | } 587 | }); 588 | assertThat(users.get(0).getId()).isEqualTo("1"); 589 | assertThat(users.get(1).getId()).isEqualTo("3"); 590 | } 591 | 592 | } 593 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://secure.travis-ci.org/nurkiewicz/spring-data-jdbc-repository.png?branch=master)](https://travis-ci.org/nurkiewicz/spring-data-jdbc-repository) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.nurkiewicz.jdbcrepository/jdbcrepository/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.nurkiewicz.jdbcrepository/jdbcrepository) 2 | 3 | # Spring Data JDBC generic DAO implementation 4 | 5 | ---- 6 | 7 | ### Check out [jirutka/spring-data-jdbc-repository](https://github.com/jirutka/spring-data-jdbc-repository) fork that is actively developed and maintained. This repository is no longer supported. 8 | 9 | ---- 10 | 11 | The purpose of this project is to provide generic, lightweight and easy to use DAO implementation for relational databases based on [`JdbcTemplate`](http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/jdbc/core/JdbcTemplate.html) from [Spring framework](http://www.springsource.org/spring-framework), compatible with Spring Data umbrella of projects. 12 | 13 | ## Design objectives 14 | 15 | * Lightweight, fast and low-overhead. Only a handful of classes, **no XML, annotations, reflection** 16 | * **This is not full-blown ORM**. No relationship handling, lazy loading, dirty checking, caching 17 | * CRUD implemented in seconds 18 | * For small applications where JPA is an overkill 19 | * Use when simplicity is needed or when future migration e.g. to JPA is considered 20 | * Minimalistic support for database dialect differences (e.g. transparent paging of results) 21 | 22 | ## Features 23 | 24 | Each DAO provides built-in support for: 25 | 26 | * Mapping to/from domain objects through [`RowMapper`](http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/jdbc/core/RowMapper.html) abstraction 27 | * Generated and user-defined primary keys 28 | * Extracting generated key 29 | * Compound (multi-column) primary keys 30 | * Immutable domain objects 31 | * Paging (requesting subset of results) 32 | * Sorting over several columns (database agnostic) 33 | * Optional support for *many-to-one* relationships 34 | * Supported databases (continuously tested): 35 | * MySQL 36 | * PostgreSQL 37 | * H2 38 | * HSQLDB 39 | * Derby 40 | * MS SQL Server (2008, 2012) 41 | * Oracle 10g / 11g (9i should work too) 42 | * ...and most likely many others 43 | * Easily extendable to other database dialects via [`SqlGenerator`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/main/java/com/nurkiewicz/jdbcrepository/sql/SqlGenerator.java) class. 44 | * Easy retrieval of records by ID 45 | 46 | ## API 47 | 48 | Compatible with Spring Data [`PagingAndSortingRepository`](http://static.springsource.org/spring-data/data-commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html) abstraction, **all these methods are implemented for you**: 49 | 50 | ```java 51 | public interface PagingAndSortingRepository extends CrudRepository { 52 | T save(T entity); 53 | Iterable save(Iterable entities); 54 | T findOne(ID id); 55 | boolean exists(ID id); 56 | Iterable findAll(); 57 | long count(); 58 | void delete(ID id); 59 | void delete(T entity); 60 | void delete(Iterable entities); 61 | void deleteAll(); 62 | Iterable findAll(Sort sort); 63 | Page findAll(Pageable pageable); 64 | Iterable findAll(Iterable ids); 65 | } 66 | ``` 67 | 68 | `Pageable` and `Sort` parameters are also fully supported, which means you get **paging and sorting by arbitrary properties for free**. For example say you have `userRepository` extending `PagingAndSortingRepository` interface (implemented for you by the library) and you request 5th page of `USERS` table, 10 per page, after applying some sorting: 69 | 70 | ```java 71 | Page page = userRepository.findAll( 72 | new PageRequest( 73 | 5, 10, 74 | new Sort( 75 | new Order(DESC, "reputation"), 76 | new Order(ASC, "user_name") 77 | ) 78 | ) 79 | ); 80 | ``` 81 | 82 | Spring Data JDBC repository library will translate this call into (PostgreSQL syntax): 83 | 84 | ```sql 85 | SELECT * 86 | FROM USERS 87 | ORDER BY reputation DESC, user_name ASC 88 | LIMIT 50 OFFSET 10 89 | ``` 90 | 91 | ...or even (Derby syntax): 92 | 93 | ```sql 94 | SELECT * FROM ( 95 | SELECT ROW_NUMBER() OVER () AS ROW_NUM, t.* 96 | FROM ( 97 | SELECT * 98 | FROM USERS 99 | ORDER BY reputation DESC, user_name ASC 100 | ) AS t 101 | ) AS a 102 | WHERE ROW_NUM BETWEEN 51 AND 60 103 | ``` 104 | 105 | No matter which database you use, you'll get `Page` object in return (you still have to provide `RowMapper` yourself to translate from [`ResultSet`](http://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html) to domain object). If you don't know Spring Data project yet, [`Page`](http://static.springsource.org/spring-data/commons/docs/current/api/org/springframework/data/domain/Page.html) is a wonderful abstraction, not only encapsulating `List`, but also providing metadata such as total number of records, on which page we currently are, etc. 106 | 107 | ## Reasons to use 108 | 109 | * You consider migration to JPA or even some NoSQL database in the future. 110 | 111 | Since your code will rely only on methods defined in [`PagingAndSortingRepository`](http://static.springsource.org/spring-data/data-commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html) and [`CrudRepository`](http://static.springsource.org/spring-data/data-commons/docs/current/api/org/springframework/data/repository/CrudRepository.html) from [Spring Data Commons](http://www.springsource.org/spring-data/commons) umbrella project you are free to switch from [`JdbcRepository`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/main/java/com/nurkiewicz/jdbcrepository/JdbcRepository.java) implementation (from this project) to: [`JpaRepository`](http://static.springsource.org/spring-data/data-jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html), [`MongoRepository`](http://static.springsource.org/spring-data/data-mongodb/docs/current/api/org/springframework/data/mongodb/repository/MongoRepository.html), [`GemfireRepository`](http://static.springsource.org/spring-data-gemfire/docs/current/api/org/springframework/data/gemfire/repository/GemfireRepository.html) or [`GraphRepository`](http://static.springsource.org/spring-data/data-graph/docs/current/api/org/springframework/data/neo4j/repository/GraphRepository.html). They all implement the same common API. Of course don't expect that switching from JDBC to JPA or MongoDB will be as simple as switching imported JAR dependencies - but at least you minimize the impact by using same DAO API. 112 | 113 | * You need a fast, simple JDBC wrapper library. JPA or even [MyBatis](http://blog.mybatis.org/) is an overkill 114 | 115 | * You want to have full control over generated SQL if needed 116 | 117 | * You want to work with objects, but don't need lazy loading, relationship handling, multi-level caching, dirty checking... You need [CRUD](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete) and not much more 118 | 119 | * You want to by [*DRY*](http://en.wikipedia.org/wiki/Don't_repeat_yourself) 120 | 121 | * You are already using Spring or maybe even [`JdbcTemplate`](http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/jdbc/core/JdbcTemplate.html), but still feel like there is too much manual work 122 | 123 | * You have very few database tables 124 | 125 | ## Getting started 126 | 127 | For more examples and working code don't forget to examine [project tests](https://github.com/nurkiewicz/spring-data-jdbc-repository/tree/master/src/test/java/com/nurkiewicz/jdbcrepository). 128 | 129 | ### Prerequisites 130 | 131 | Maven coordinates: 132 | 133 | ```xml 134 | 135 | com.nurkiewicz.jdbcrepository 136 | jdbcrepository 137 | 0.4 138 | 139 | ``` 140 | 141 | This project is available under maven central repository. 142 | 143 | Alternatively you can [download source code as ZIP](https://github.com/nurkiewicz/spring-data-jdbc-repository/tags). 144 | 145 | --- 146 | 147 | In order to start your project must have `DataSource` bean present and transaction management enabled. Here is a minimal MySQL configuration: 148 | 149 | ```java 150 | @EnableTransactionManagement 151 | @Configuration 152 | public class MinimalConfig { 153 | 154 | @Bean 155 | public PlatformTransactionManager transactionManager() { 156 | return new DataSourceTransactionManager(dataSource()); 157 | } 158 | 159 | @Bean 160 | public DataSource dataSource() { 161 | MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); 162 | ds.setUser("user"); 163 | ds.setPassword("secret"); 164 | ds.setDatabaseName("db_name"); 165 | return ds; 166 | } 167 | 168 | } 169 | ``` 170 | 171 | ### Entity with auto-generated key 172 | 173 | Say you have a following database table with auto-generated key (MySQL syntax): 174 | 175 | ```sql 176 | CREATE TABLE COMMENTS ( 177 | id INT AUTO_INCREMENT, 178 | user_name varchar(256), 179 | contents varchar(1000), 180 | created_time TIMESTAMP NOT NULL, 181 | PRIMARY KEY (id) 182 | ); 183 | ``` 184 | 185 | First you need to create domain object [`User`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/test/java/com/nurkiewicz/jdbcrepository/repositories/User.java) mapping to that table (just like in any other ORM): 186 | 187 | ```java 188 | public class Comment implements Persistable { 189 | 190 | private Integer id; 191 | private String userName; 192 | private String contents; 193 | private Date createdTime; 194 | 195 | @Override 196 | public Integer getId() { 197 | return id; 198 | } 199 | 200 | @Override 201 | public boolean isNew() { 202 | return id == null; 203 | } 204 | 205 | //getters/setters/constructors/... 206 | } 207 | ``` 208 | 209 | Apart from standard Java boilerplate you should notice implementing [`Persistable`](http://static.springsource.org/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html) where `Integer` is the type of primary key. `Persistable` is an interface coming from Spring Data project and it's the only requirement we place on your domain object. 210 | 211 | Finally we are ready to create our [`CommentRepository`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/test/java/com/nurkiewicz/jdbcrepository/repositories/CommentRepository.java) DAO: 212 | 213 | ```java 214 | @Repository 215 | public class CommentRepository extends JdbcRepository { 216 | 217 | public CommentRepository() { 218 | super(ROW_MAPPER, ROW_UNMAPPER, "COMMENTS"); 219 | } 220 | 221 | public static final RowMapper ROW_MAPPER = //see below 222 | 223 | private static final RowUnmapper ROW_UNMAPPER = //see below 224 | 225 | @Override 226 | protected S postCreate(S entity, Number generatedId) { 227 | entity.setId(generatedId.intValue()); 228 | return entity; 229 | } 230 | } 231 | ``` 232 | 233 | First of all we use [`@Repository`](http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/stereotype/Repository.html) annotation to mark DAO bean. It enables persistence exception translation. Also such annotated beans are discovered by CLASSPATH scanning. 234 | 235 | As you can see we extend `JdbcRepository` which is the central class of this library, providing implementations of all `PagingAndSortingRepository` methods. Its constructor has three required dependencies: `RowMapper`, [`RowUnmapper`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/main/java/com/nurkiewicz/jdbcrepository/RowUnmapper.java) and table name. You may also provide ID column name, otherwise default `"id"` is used. 236 | 237 | If you ever used `JdbcTemplate` from Spring, you should be familiar with [`RowMapper`](http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/jdbc/core/RowMapper.html) interface. We need to somehow extract columns from `ResultSet` into an object. After all we don't want to work with raw JDBC results. It's quite straightforward: 238 | 239 | ```java 240 | public static final RowMapper ROW_MAPPER = new RowMapper() { 241 | @Override 242 | public Comment mapRow(ResultSet rs, int rowNum) throws SQLException { 243 | return new Comment( 244 | rs.getInt("id"), 245 | rs.getString("user_name"), 246 | rs.getString("contents"), 247 | rs.getTimestamp("created_time") 248 | ); 249 | } 250 | }; 251 | ``` 252 | 253 | `RowUnmapper` comes from this library and it's essentially the opposite of `RowMapper`: takes an object and turns it into a `Map`. This map is later used by the library to construct SQL `CREATE`/`UPDATE` queries: 254 | 255 | ```java 256 | private static final RowUnmapper ROW_UNMAPPER = new RowUnmapper() { 257 | @Override 258 | public Map mapColumns(Comment comment) { 259 | Map mapping = new LinkedHashMap(); 260 | mapping.put("id", comment.getId()); 261 | mapping.put("user_name", comment.getUserName()); 262 | mapping.put("contents", comment.getContents()); 263 | mapping.put("created_time", new java.sql.Timestamp(comment.getCreatedTime().getTime())); 264 | return mapping; 265 | } 266 | }; 267 | ``` 268 | 269 | If you never update your database table (just reading some reference data inserted elsewhere) you may skip `RowUnmapper` parameter or use [`MissingRowUnmapper`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/main/java/com/nurkiewicz/jdbcrepository/MissingRowUnmapper.java). 270 | 271 | Last piece of the puzzle is the `postCreate()` callback method which is called after an object was inserted. You can use it to retrieve generated primary key and update your domain object (or return new one if your domain objects are immutable). If you don't need it, just don't override `postCreate()`. 272 | 273 | Check out [`JdbcRepositoryGeneratedKeyTest`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/test/java/com/nurkiewicz/jdbcrepository/JdbcRepositoryGeneratedKeyTest.java) for a working code based on this example. 274 | 275 | > By now you might have a feeling that, compared to JPA or Hibernate, there is quite a lot of manual work. However various JPA implementations and other ORM frameworks are notoriously known for introducing significant overhead and manifesting some learning curve. This tiny library intentionally leaves some responsibilities to the user in order to avoid complex mappings, reflection, annotations... all the implicitness that is not always desired. 276 | 277 | > This project is not intending to replace mature and stable ORM frameworks. Instead it tries to fill in a niche between raw JDBC and ORM where simplicity and low overhead are key features. 278 | 279 | ### Entity with manually assigned key 280 | 281 | In this example we'll see how entities with user-defined primary keys are handled. Let's start from database model: 282 | 283 | ```java 284 | CREATE TABLE USERS ( 285 | user_name varchar(255), 286 | date_of_birth TIMESTAMP NOT NULL, 287 | enabled BIT(1) NOT NULL, 288 | PRIMARY KEY (user_name) 289 | ); 290 | ``` 291 | 292 | ...and [`User`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/test/java/com/nurkiewicz/jdbcrepository/repositories/User.java) domain model: 293 | 294 | ```java 295 | public class User implements Persistable { 296 | 297 | private transient boolean persisted; 298 | 299 | private String userName; 300 | private Date dateOfBirth; 301 | private boolean enabled; 302 | 303 | @Override 304 | public String getId() { 305 | return userName; 306 | } 307 | 308 | @Override 309 | public boolean isNew() { 310 | return !persisted; 311 | } 312 | 313 | public void setPersisted(boolean persisted) { 314 | this.persisted = persisted; 315 | } 316 | 317 | //getters/setters/constructors/... 318 | 319 | } 320 | ``` 321 | 322 | Notice that special `persisted` transient flag was added. Contract of [`CrudRepository.save()`](http://static.springsource.org/spring-data/data-commons/docs/current/api/org/springframework/data/repository/CrudRepository.html#save(S)) from Spring Data project requires that an entity knows whether it was already saved or not (`isNew()`) method - there are no separate `create()` and `update()` methods. Implementing `isNew()` is simple for auto-generated keys (see `Comment` above) but in this case we need an extra transient field. If you hate this workaround and you only insert data and never update, you'll get away with return `true` all the time from `isNew()`. 323 | 324 | And finally our DAO, [`UserRepository`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/test/java/com/nurkiewicz/jdbcrepository/repositories/UserRepository.java) bean: 325 | 326 | ```java 327 | @Repository 328 | public class UserRepository extends JdbcRepository { 329 | 330 | public UserRepository() { 331 | super(ROW_MAPPER, ROW_UNMAPPER, "USERS", "user_name"); 332 | } 333 | 334 | public static final RowMapper ROW_MAPPER = //... 335 | 336 | public static final RowUnmapper ROW_UNMAPPER = //... 337 | 338 | @Override 339 | protected S postUpdate(S entity) { 340 | entity.setPersisted(true); 341 | return entity; 342 | } 343 | 344 | @Override 345 | protected S postCreate(S entity, Number generatedId) { 346 | entity.setPersisted(true); 347 | return entity; 348 | } 349 | } 350 | ``` 351 | 352 | `"USERS"` and `"user_name"` parameters designate table name and primary key column name. I'll leave the details of mapper and unmapper (see [source code]((https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/test/java/com/nurkiewicz/jdbcrepository/repositories/UserRepository.java))). But please notice `postUpdate()` and `postCreate()` methods. They ensure that once object was persisted, `persisted` flag is set so that subsequent calls to `save()` will update existing entity rather than trying to reinsert it. 353 | 354 | Check out [`JdbcRepositoryManualKeyTest`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/test/java/com/nurkiewicz/jdbcrepository/JdbcRepositoryManualKeyTest.java) for a working code based on this example. 355 | 356 | ### Compound primary key 357 | 358 | We also support compound primary keys (primary keys consisting of several columns). Take this table as an example: 359 | 360 | ```sql 361 | CREATE TABLE BOARDING_PASS ( 362 | flight_no VARCHAR(8) NOT NULL, 363 | seq_no INT NOT NULL, 364 | passenger VARCHAR(1000), 365 | seat CHAR(3), 366 | PRIMARY KEY (flight_no, seq_no) 367 | ); 368 | ``` 369 | 370 | I would like you to notice the type of primary key in `Persistable`: 371 | 372 | ```java 373 | public class BoardingPass implements Persistable { 374 | 375 | private transient boolean persisted; 376 | 377 | private String flightNo; 378 | private int seqNo; 379 | private String passenger; 380 | private String seat; 381 | 382 | @Override 383 | public Object[] getId() { 384 | return pk(flightNo, seqNo); 385 | } 386 | 387 | @Override 388 | public boolean isNew() { 389 | return !persisted; 390 | } 391 | 392 | //getters/setters/constructors/... 393 | 394 | } 395 | ``` 396 | 397 | Unfortunately library does not support small, immutable value classes encapsulating all ID values in one object (like JPA does with [`@IdClass`](http://docs.oracle.com/javaee/6/api/javax/persistence/IdClass.html)), so you have to live with `Object[]` array. Defining DAO class is similar to what we've already seen: 398 | 399 | ```java 400 | public class BoardingPassRepository extends JdbcRepository { 401 | public BoardingPassRepository() { 402 | this("BOARDING_PASS"); 403 | } 404 | 405 | public BoardingPassRepository(String tableName) { 406 | super(MAPPER, UNMAPPER, new TableDescription(tableName, null, "flight_no", "seq_no") 407 | ); 408 | } 409 | 410 | public static final RowMapper ROW_MAPPER = //... 411 | 412 | public static final RowUnmapper UNMAPPER = //... 413 | 414 | } 415 | ``` 416 | Two things to notice: we extend `JdbcRepository` and we provide two ID column names just as expected: `"flight_no", "seq_no"`. We query such DAO by providing both `flight_no` and `seq_no` (necessarily in that order) values wrapped by `Object[]`: 417 | 418 | ```java 419 | BoardingPass pass = boardingPassRepository.findOne(new Object[] {"FOO-1022", 42}); 420 | ``` 421 | 422 | No doubts, this is cumbersome in practice, so we provide tiny helper method which you can statically import: 423 | 424 | ```java 425 | import static com.nurkiewicz.jdbcrepository.JdbcRepository.pk; 426 | //... 427 | 428 | BoardingPass foundFlight = boardingPassRepository.findOne(pk("FOO-1022", 42)); 429 | ``` 430 | 431 | Check out [`JdbcRepositoryCompoundPkTest`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/test/java/com/nurkiewicz/jdbcrepository/JdbcRepositoryCompoundPkTest.java) for a working code based on this example. 432 | 433 | ### Transactions 434 | 435 | This library is completely orthogonal to transaction management. Every method of each repository requires running transaction and it's up to you to set it up. Typically you would place `@Transactional` on service layer (calling DAO beans). I don't recommend [placing `@Transactional` over every DAO bean](http://stackoverflow.com/questions/8993318/what-is-the-right-way-to-use-spring-mvc-with-hibernate-in-dao-sevice-layer-arch). 436 | 437 | ## Caching 438 | 439 | Spring Data JDBC repository library is not providing any caching abstraction or support. However adding `@Cacheable` layer on top of your DAOs or services using [caching abstraction in Spring](http://static.springsource.org/spring/docs/3.1.0.RELEASE/spring-framework-reference/html/cache.html) is quite straightforward. See also: [*@Cacheable overhead in Spring*](http://nurkiewicz.blogspot.no/2013/01/cacheable-overhead-in-spring.html). 440 | 441 | ## Contributions 442 | 443 | ..are always welcome. Don't hesitate to [submit bug reports](https://github.com/nurkiewicz/spring-data-jdbc-repository/issues) and [pull requests](https://github.com/nurkiewicz/spring-data-jdbc-repository/pulls). 444 | 445 | ### Testing 446 | 447 | This library is continuously tested using Travis ([![Build Status](https://secure.travis-ci.org/nurkiewicz/spring-data-jdbc-repository.png?branch=master)](https://travis-ci.org/nurkiewicz/spring-data-jdbc-repository)). Test suite consists of 60+ distinct tests each run against 8 different databases: MySQL, PostgreSQL, H2, HSQLDB and Derby + MS SQL Server and Oracle tests not run as part of CI. 448 | 449 | When filling [bug reports](https://github.com/nurkiewicz/spring-data-jdbc-repository/issues) or submitting new features please try including supporting test cases. Each [pull request](https://github.com/nurkiewicz/spring-data-jdbc-repository/pulls) is automatically tested on a separate branch. 450 | 451 | ### Building 452 | 453 | After forking the [official repository](https://github.com/nurkiewicz/spring-data-jdbc-repository) building is as simple as running: 454 | 455 | ```bash 456 | $ mvn install 457 | ``` 458 | 459 | You'll notice plenty of exceptions during JUnit test execution. This is normal. Some of the tests run against MySQL and PostgreSQL available only on Travis CI server. When these database servers are unavailable, whole test is simply *skipped*: 460 | 461 | ``` 462 | Results : 463 | 464 | Tests run: 484, Failures: 0, Errors: 0, Skipped: 295 465 | ``` 466 | 467 | Exception stack traces come from root [`AbstractIntegrationTest`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/test/java/com/nurkiewicz/jdbcrepository/AbstractIntegrationTest.java). 468 | 469 | ## Design 470 | 471 | Library consists of only a handful of classes, highlighted in the diagram below ([source](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/main/docs/yuml.txt)): 472 | 473 | ![UML diagram](https://raw.github.com/nurkiewicz/spring-data-jdbc-repository/master/src/main/docs/classes.png) 474 | 475 | [`JdbcRepository`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/main/java/com/nurkiewicz/jdbcrepository/JdbcRepository.java) is the most important class that implements all [`PagingAndSortingRepository`](http://static.springsource.org/spring-data/data-commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html) methods. Each user repository has to extend this class. Also each such repository must at least implement [`RowMapper`](http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/jdbc/core/RowMapper.html) and [`RowUnmapper`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/main/java/com/nurkiewicz/jdbcrepository/RowUnmapper.java) (only if you want to modify table data). 476 | 477 | SQL generation is delegated to [`SqlGenerator`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/main/java/com/nurkiewicz/jdbcrepository/sql/SqlGenerator.java). [`PostgreSqlGenerator.`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/main/java/com/nurkiewicz/jdbcrepository/sql/PostgreSqlGenerator.java) and [`DerbySqlGenerator`](https://github.com/nurkiewicz/spring-data-jdbc-repository/blob/master/src/main/java/com/nurkiewicz/jdbcrepository/sql/DerbySqlGenerator.java) are provided for databases that don't work with standard generator. 478 | 479 | ## Changelog 480 | 481 | ### 0.4.1 482 | 483 | * Fixed [*Standalone Configuration and CDI Implementation*](https://github.com/nurkiewicz/spring-data-jdbc-repository/issues/10) 484 | 485 | ### 0.4 486 | 487 | * Repackaged: `com.blogspot.nurkiewicz` -> `com.nurkiewicz` 488 | 489 | ### 0.3.2 490 | 491 | * First version available in Maven central repository 492 | * Upgraded Spring Data Commons 1.6.1 -> 1.8.0 493 | 494 | ### 0.3.1 495 | 496 | * Upgraded Spring dependencies: 3.2.1 -> 3.2.4 and 1.5.0 -> 1.6.1 497 | * Fixed [#5 Allow manually injecting JdbcOperations, SqlGenerator and DataSource](https://github.com/nurkiewicz/spring-data-jdbc-repository/issues/5) 498 | 499 | ### 0.3 500 | 501 | * Oracle 10g / 11g support (see [pull request](https://github.com/nurkiewicz/spring-data-jdbc-repository/pull/3)) 502 | * Upgrading Spring dependency to 3.2.1.RELEASE and [Spring Data Commons](http://www.springsource.org/spring-data/commons) to 1.5.0.RELEASE (see [#4](https://github.com/nurkiewicz/spring-data-jdbc-repository/issues/4)). 503 | 504 | ### 0.2 505 | 506 | * MS SQL Server 2008/2012 support (see [pull request](https://github.com/nurkiewicz/spring-data-jdbc-repository/pull/2)) 507 | 508 | ### 0.1 509 | 510 | * Initial revision ([announcement](http://nurkiewicz.blogspot.no/2013/01/spring-data-jdbc-generic-dao.html)) 511 | 512 | ## License 513 | This project is released under version 2.0 of the [Apache License](http://www.apache.org/licenses/LICENSE-2.0) (same as [Spring framework](https://github.com/SpringSource/spring-framework)). 514 | --------------------------------------------------------------------------------