diff --git a/jooq-spring/.classpath b/jooq-spring/.classpath
new file mode 100644
index 0000000000..e43402fa4f
--- /dev/null
+++ b/jooq-spring/.classpath
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jooq-spring/.project b/jooq-spring/.project
new file mode 100644
index 0000000000..3c3f3d602b
--- /dev/null
+++ b/jooq-spring/.project
@@ -0,0 +1,23 @@
+
+
+ jooq-spring
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/jooq-spring/pom.xml b/jooq-spring/pom.xml
new file mode 100644
index 0000000000..80eba86164
--- /dev/null
+++ b/jooq-spring/pom.xml
@@ -0,0 +1,159 @@
+
+ 4.0.0
+ com.baeldung
+ jooq-spring
+ 0.0.1-SNAPSHOT
+
+
+ 3.7.3
+ 1.4.191
+ 2.4.4
+ 4.2.5.RELEASE
+ 1.7.18
+ 1.1.3
+ 4.12
+
+
+
+
+
+ org.jooq
+ jooq
+ ${org.jooq.version}
+
+
+
+
+ com.h2database
+ h2
+ ${com.h2database.version}
+
+
+ com.zaxxer
+ HikariCP
+ ${com.zaxxer.HikariCP.version}
+
+
+
+
+ org.springframework
+ spring-context
+ ${org.springframework.version}
+
+
+ org.springframework
+ spring-jdbc
+ ${org.springframework.version}
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${org.slf4j.version}
+ runtime
+
+
+ ch.qos.logback
+ logback-classic
+ ${ch.qos.logback.version}
+ runtime
+
+
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ org.springframework
+ spring-test
+ ${org.springframework.version}
+ test
+
+
+
+
+
+
+ org.codehaus.mojo
+ properties-maven-plugin
+ 1.0.0
+
+
+ initialize
+
+ read-project-properties
+
+
+
+ src/main/resources/intro_config.properties
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ sql-maven-plugin
+ 1.5
+
+
+ initialize
+
+ execute
+
+
+ ${db.driver}
+ ${db.url}
+ ${db.username}
+ ${db.password}
+
+ src/main/resources/intro_schema.sql
+
+
+
+
+
+
+ com.h2database
+ h2
+ ${com.h2database.version}
+
+
+
+
+
+ org.jooq
+ jooq-codegen-maven
+ ${org.jooq.version}
+
+
+ generate-sources
+
+ generate
+
+
+
+ ${db.driver}
+ ${db.url}
+ ${db.username}
+ ${db.password}
+
+
+
+ com.baeldung.jooq.introduction.db
+ src/test/java
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jooq-spring/src/main/resources/intro_config.properties b/jooq-spring/src/main/resources/intro_config.properties
new file mode 100644
index 0000000000..3275089585
--- /dev/null
+++ b/jooq-spring/src/main/resources/intro_config.properties
@@ -0,0 +1,8 @@
+#Database Configuration
+db.driver=org.h2.Driver
+db.url=jdbc:h2:~/jooq
+db.username=sa
+db.password=
+
+#SQL Dialect
+jooq.sql.dialect=H2
\ No newline at end of file
diff --git a/jooq-spring/src/main/resources/intro_schema.sql b/jooq-spring/src/main/resources/intro_schema.sql
new file mode 100644
index 0000000000..0d4bd26235
--- /dev/null
+++ b/jooq-spring/src/main/resources/intro_schema.sql
@@ -0,0 +1,34 @@
+DROP TABLE IF EXISTS author_book, author, book;
+
+CREATE TABLE author (
+ id INT NOT NULL PRIMARY KEY,
+ first_name VARCHAR(50),
+ last_name VARCHAR(50) NOT NULL
+);
+
+CREATE TABLE book (
+ id INT NOT NULL PRIMARY KEY,
+ title VARCHAR(100) NOT NULL
+);
+
+CREATE TABLE author_book (
+ author_id INT NOT NULL,
+ book_id INT NOT NULL,
+
+ PRIMARY KEY (author_id, book_id),
+ CONSTRAINT fk_ab_author FOREIGN KEY (author_id) REFERENCES author (id)
+ ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT fk_ab_book FOREIGN KEY (book_id) REFERENCES book (id)
+);
+
+INSERT INTO author VALUES
+ (1, 'Kathy', 'Sierra'),
+ (2, 'Bert', 'Bates'),
+ (3, 'Bryan', 'Basham');
+
+INSERT INTO book VALUES
+ (1, 'Head First Java'),
+ (2, 'Head First Servlets and JSP'),
+ (3, 'OCA/OCP Java SE 7 Programmer');
+
+INSERT INTO author_book VALUES (1, 1), (1, 3), (2, 1);
\ No newline at end of file
diff --git a/jooq-spring/src/test/java/com/baeldung/jooq/introduction/ExceptionTranslator.java b/jooq-spring/src/test/java/com/baeldung/jooq/introduction/ExceptionTranslator.java
new file mode 100644
index 0000000000..7bee21f077
--- /dev/null
+++ b/jooq-spring/src/test/java/com/baeldung/jooq/introduction/ExceptionTranslator.java
@@ -0,0 +1,19 @@
+package com.baeldung.jooq.introduction;
+
+import org.jooq.ExecuteContext;
+import org.jooq.SQLDialect;
+import org.jooq.impl.DefaultExecuteListener;
+
+import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
+import org.springframework.jdbc.support.SQLExceptionTranslator;
+
+public class ExceptionTranslator extends DefaultExecuteListener {
+ private static final long serialVersionUID = 649359748808106775L;
+
+ @Override
+ public void exception(ExecuteContext context) {
+ SQLDialect dialect = context.configuration().dialect();
+ SQLExceptionTranslator translator = new SQLErrorCodeSQLExceptionTranslator(dialect.name());
+ context.exception(translator.translate("Access database using jOOQ", context.sql(), context.sqlException()));
+ }
+}
\ No newline at end of file
diff --git a/jooq-spring/src/test/java/com/baeldung/jooq/introduction/PersistenceContext.java b/jooq-spring/src/test/java/com/baeldung/jooq/introduction/PersistenceContext.java
new file mode 100644
index 0000000000..4bcf3a527e
--- /dev/null
+++ b/jooq-spring/src/test/java/com/baeldung/jooq/introduction/PersistenceContext.java
@@ -0,0 +1,79 @@
+package com.baeldung.jooq.introduction;
+
+import javax.sql.DataSource;
+import com.zaxxer.hikari.HikariDataSource;
+
+import org.jooq.SQLDialect;
+import org.jooq.impl.DataSourceConnectionProvider;
+import org.jooq.impl.DefaultConfiguration;
+import org.jooq.impl.DefaultDSLContext;
+import org.jooq.impl.DefaultExecuteListenerProvider;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.core.env.Environment;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@Configuration
+@ComponentScan({ "com.baeldung.jooq.introduction.db.public_.tables" })
+@EnableTransactionManagement
+@PropertySource("classpath:intro_config.properties")
+public class PersistenceContext {
+ @Autowired
+ private Environment environment;
+
+ @Bean
+ public DataSource dataSource() {
+ HikariDataSource dataSource = new HikariDataSource();
+
+ dataSource.setDriverClassName(environment.getRequiredProperty("db.driver"));
+ dataSource.setJdbcUrl(environment.getRequiredProperty("db.url"));
+ dataSource.setUsername(environment.getRequiredProperty("db.username"));
+ dataSource.setPassword(environment.getRequiredProperty("db.password"));
+
+ return dataSource;
+ }
+
+ @Bean
+ public TransactionAwareDataSourceProxy transactionAwareDataSource() {
+ return new TransactionAwareDataSourceProxy(dataSource());
+ }
+
+ @Bean
+ public DataSourceTransactionManager transactionManager() {
+ return new DataSourceTransactionManager(dataSource());
+ }
+
+ @Bean
+ public DataSourceConnectionProvider connectionProvider() {
+ return new DataSourceConnectionProvider(transactionAwareDataSource());
+ }
+
+ @Bean
+ public ExceptionTranslator exceptionTransformer() {
+ return new ExceptionTranslator();
+ }
+
+ @Bean
+ public DefaultDSLContext dsl() {
+ return new DefaultDSLContext(configuration());
+ }
+
+ @Bean
+ public DefaultConfiguration configuration() {
+ DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
+ jooqConfiguration.set(connectionProvider());
+ jooqConfiguration.set(new DefaultExecuteListenerProvider(exceptionTransformer()));
+
+ String sqlDialectName = environment.getRequiredProperty("jooq.sql.dialect");
+ SQLDialect dialect = SQLDialect.valueOf(sqlDialectName);
+ jooqConfiguration.set(dialect);
+
+ return jooqConfiguration;
+ }
+}
\ No newline at end of file
diff --git a/jooq-spring/src/test/java/com/baeldung/jooq/introduction/QueryTest.java b/jooq-spring/src/test/java/com/baeldung/jooq/introduction/QueryTest.java
new file mode 100644
index 0000000000..34be23bf2a
--- /dev/null
+++ b/jooq-spring/src/test/java/com/baeldung/jooq/introduction/QueryTest.java
@@ -0,0 +1,87 @@
+package com.baeldung.jooq.introduction;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.jooq.DSLContext;
+import org.jooq.Record3;
+import org.jooq.Result;
+import org.jooq.impl.DSL;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.baeldung.jooq.introduction.db.public_.tables.Author;
+import com.baeldung.jooq.introduction.db.public_.tables.AuthorBook;
+import com.baeldung.jooq.introduction.db.public_.tables.Book;
+
+@ContextConfiguration(classes = PersistenceContext.class)
+@Transactional(transactionManager = "transactionManager")
+@RunWith(SpringJUnit4ClassRunner.class)
+public class QueryTest {
+ @Autowired
+ private DSLContext create;
+
+ Author author = Author.AUTHOR;
+ Book book = Book.BOOK;
+ AuthorBook authorBook = AuthorBook.AUTHOR_BOOK;
+
+ @Test
+ public void givenValidData_whenInserting_thenSucceed() {
+ create.insertInto(author).set(author.ID, 4).set(author.FIRST_NAME, "Herbert").set(author.LAST_NAME, "Schildt").execute();
+ create.insertInto(book).set(book.ID, 4).set(book.TITLE, "A Beginner's Guide").execute();
+ create.insertInto(authorBook).set(authorBook.AUTHOR_ID, 4).set(authorBook.BOOK_ID, 4).execute();
+ Result> result = create.select(author.ID, author.LAST_NAME, DSL.count()).from(author).join(authorBook).on(author.ID.equal(authorBook.AUTHOR_ID)).join(book).on(authorBook.BOOK_ID.equal(book.ID))
+ .groupBy(author.LAST_NAME).fetch();
+
+ assertEquals(3, result.size());
+ assertEquals("Sierra", result.getValue(0, author.LAST_NAME));
+ assertEquals(Integer.valueOf(2), result.getValue(0, DSL.count()));
+ assertEquals("Schildt", result.getValue(2, author.LAST_NAME));
+ assertEquals(Integer.valueOf(1), result.getValue(2, DSL.count()));
+ }
+
+ @Test(expected = DataAccessException.class)
+ public void givenInvalidData_whenInserting_thenFail() {
+ create.insertInto(authorBook).set(authorBook.AUTHOR_ID, 4).set(authorBook.BOOK_ID, 5).execute();
+ }
+
+ @Test
+ public void givenValidData_whenUpdating_thenSucceed() {
+ create.update(author).set(author.LAST_NAME, "Baeldung").where(author.ID.equal(3)).execute();
+ create.update(book).set(book.TITLE, "Building your REST API with Spring").where(book.ID.equal(3)).execute();
+ create.insertInto(authorBook).set(authorBook.AUTHOR_ID, 3).set(authorBook.BOOK_ID, 3).execute();
+ Result> result = create.select(author.ID, author.LAST_NAME, book.TITLE).from(author).join(authorBook).on(author.ID.equal(authorBook.AUTHOR_ID)).join(book).on(authorBook.BOOK_ID.equal(book.ID)).where(author.ID.equal(3))
+ .fetch();
+
+ assertEquals(1, result.size());
+ assertEquals(Integer.valueOf(3), result.getValue(0, author.ID));
+ assertEquals("Baeldung", result.getValue(0, author.LAST_NAME));
+ assertEquals("Building your REST API with Spring", result.getValue(0, book.TITLE));
+ }
+
+ @Test(expected = DataAccessException.class)
+ public void givenInvalidData_whenUpdating_thenFail() {
+ create.update(authorBook).set(authorBook.AUTHOR_ID, 4).set(authorBook.BOOK_ID, 5).execute();
+ }
+
+ @Test
+ public void givenValidData_whenDeleting_thenSucceed() {
+ create.delete(author).where(author.ID.lt(3)).execute();
+ Result> result = create.select(author.ID, author.FIRST_NAME, author.LAST_NAME).from(author).fetch();
+
+ assertEquals(1, result.size());
+ assertEquals("Bryan", result.getValue(0, author.FIRST_NAME));
+ assertEquals("Basham", result.getValue(0, author.LAST_NAME));
+ }
+
+ @Test(expected = DataAccessException.class)
+ public void givenInvalidData_whenDeleting_thenFail() {
+ create.delete(book).where(book.ID.equal(1)).execute();
+ }
+}
\ No newline at end of file