├── .gitignore
├── LICENSE
├── NOTES.md
├── NOTICE
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── adamlewis
│ └── guice
│ └── persist
│ └── jooq
│ ├── JdbcLocalTxnInterceptor.java
│ ├── JooqPersistModule.java
│ └── JooqPersistService.java
└── test
├── java
└── com
│ └── adamlewis
│ └── guice
│ └── persist
│ └── jooq
│ ├── JdbcLocalTxnInterceptorTest.java
│ ├── JooqPersistServiceTest.java
│ ├── modules
│ ├── ConfigurationModule.java
│ ├── DataSourceModule.java
│ └── SettingsModule.java
│ └── utils
│ ├── MockConnection.java
│ └── Providers.java
└── resources
└── logback-test.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | *.iml
3 | .idea/
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
--------------------------------------------------------------------------------
/NOTES.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | ## Version 2.0.0
4 | Thanks to @apptio-msobala for contributing the major changes and testing of this release
5 | - (fix) Reuse acquired connection in transaction configuration
6 | - (enhancement) Use Guice _Optional_ binder for `Configuration` and `Settings`
7 | - (enhancement) Reduce repeated warnings about ignored Settings (logged on Service construction only)
8 | - (breaking) Drop Java 7 support. Requires Java 8 or above.
9 | - (dependency) Bumped jOOQ minor version to 3.14 (last OSS version supporting Java 8)
10 | - (dependency) Bumped Guice to 5.1.0
11 |
12 |
13 | ## Version 1.1.0
14 | - (enhancement) `DataSource` is now injected to the (normally singleton) service using a Guice `Provider` to allow the
15 | transaction context's scope control over data source creation. Thanks @mrohan01
16 |
17 |
18 | ## Version 1.0.0
19 | - (breaking) Injector provided `DSLContext` no longer starts `UnitOfWork` automatically. Clients must explicitly
20 | control transaction boundaries or use `@Transactional` annotations; fixes #9
21 | - (dependency) Removed dependency on Guava
22 | - (dependency) jOOQ is now `provided` scope
23 | - (dependency) `slf4j-api` upgraded from `1.7.5` to `1.7.25`
24 |
25 | ## Version 0.2.0
26 | - Added several tests for transaction interceptor
27 | - Fix issue where auto-commit wouldn't be re-enabled in certain execption cases
28 |
29 | ## Version 0.1.5
30 | - `Configuration` `@Inject`ions now properly optional; fixes #4
31 |
32 | ## Version 0.1.4
33 | - Added jOOQ `Configuration` as injectable property
34 |
35 | ## Version 0.1.2
36 | - Bumped jOOQ major version (3.5.0)
37 |
38 | ## Version 0.1.1
39 | - Initial public release
40 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Portions based on work originally "Copyright (C) 2010 Google, Inc." and Licensed under the Apache License, Version 2.0:
2 | https://code.google.com/p/google-guice/source/browse/extensions/persist/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Guice Persist / jOOQ Integration
2 |
3 | A simple integration between Guice's [persistence extensions](https://code.google.com/p/google-guice/wiki/GuicePersist) and [jOOQ](http://jooq.org/). Follows closely in the pattern of the JPA persistence extension written by Dhanji R. Prasanna (dhanji@gmail.com).
4 |
5 | ## Maven Coordinates
6 |
7 | The project is deployed to Maven Central:
8 |
9 |
10 | com.adamlewis
11 | guice-persist-jooq
12 | 2.0.0
13 |
14 |
15 | ## Basic Usage
16 | See [Guice Persist](https://github.com/google/guice/wiki/GuicePersist) and [Transactions and Units of Work](https://github.com/google/guice/wiki/Transactions) for a reference on the basic semantics of the Guice Persist extension.
17 |
18 | In your module, install a new `com.adamlewis.guice.persist.jooq.JooqPersistModule` and then provide bindings for `javax.sql.DataSource` and `org.jooq.SQLDialect`. Optionally, a binding for `org.jooq.Configuration` can be provided, to customize the creation of the `org.jooq.DSLContext` instance. Then write `@Inject`able DAOs which depend on `org.jooq.DSLContext`.
19 |
20 | ## Example
21 |
22 | Here is an example Guice module written to connect guice-persist-jooq up to the [Dropwizard](https://dropwizard.github.io/dropwizard/) connection factory:
23 |
24 | import javax.sql.DataSource;
25 |
26 | import org.jooq.SQLDialect;
27 |
28 | import com.adamlewis.guice.persist.jooq.JooqPersistModule;
29 | import com.google.inject.AbstractModule;
30 | import com.google.inject.Provides;
31 | import com.yammer.dropwizard.db.DatabaseConfiguration;
32 | import com.yammer.dropwizard.db.ManagedDataSource;
33 | import com.yammer.dropwizard.db.ManagedDataSourceFactory;
34 |
35 | public class MyPersistenceModule extends AbstractModule {
36 |
37 | private final DatabaseConfiguration configuration;
38 |
39 | public MyPersistenceModule(final DatabaseConfiguration configuration) {
40 | this.configuration = configuration;
41 | }
42 |
43 | @Override
44 | protected void configure() {
45 | install(new JooqPersistModule());
46 | bind(DataSource.class).to(ManagedDataSource.class);
47 | }
48 |
49 |
50 | @Provides ManagedDataSource dataSource(final ManagedDataSourceFactory factory) throws ClassNotFoundException {
51 | return factory.build(configuration);
52 | }
53 |
54 | @Provides
55 | public SQLDialect dialect() {
56 | //TODO read from DB configuration
57 | return SQLDialect.POSTGRES;
58 | }
59 |
60 | // Optional, for full customization of the creation.
61 | @Provides
62 | public Configuration configuration(DataSource dataSource, SQLDialect dialect) {
63 | return new DefaultConfiguration()
64 | .set(dataSource)
65 | .set(new MaybeCustomMapperProvider())
66 | .set(dialect);
67 | }
68 | }
69 |
70 | And here is an example of what a DAO might look like:
71 |
72 | public class UserDao {
73 |
74 | private final DSLContext create;
75 |
76 | @Inject
77 | public UserDao(final DSLContext dsl) {
78 | this.create = dsl;
79 | }
80 |
81 |
82 | public List getUsernames() {
83 | return create.selectDistinct(User.USER.NAME).from(User.USER).fetch(User.USER.NAME);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | org.sonatype.oss
5 | oss-parent
6 | 7
7 |
8 |
9 | com.adamlewis
10 | guice-persist-jooq
11 | 2.0.1-SNAPSHOT
12 | jar
13 |
14 | Guice Persist jOOQ extension
15 | Guice-persist extension for using jOOQ based persistence layer
16 | https://github.com/supercargo/guice-persist-jooq
17 |
18 |
19 |
20 | Apache License, Version 2.0
21 | http://www.apache.org/licenses/LICENSE-2.0.txt
22 | repo
23 |
24 |
25 |
26 |
27 | https://github.com/supercargo/guice-persist-jooq
28 | scm:git:git@github.com:supercargo/guice-persist-jooq.git
29 | scm:git:git@github.com:supercargo/guice-persist-jooq.git
30 | HEAD
31 |
32 |
33 |
34 |
35 | Adam Lewis
36 | github@adamlewis.com
37 |
38 |
39 |
40 |
41 |
42 | com.google.inject.extensions
43 | guice-persist
44 | 5.1.0
45 |
46 |
47 | org.jooq
48 | jooq
49 | 3.14.0
50 | provided
51 |
52 |
53 | org.slf4j
54 | slf4j-api
55 | 1.7.25
56 |
57 |
58 |
59 | junit
60 | junit
61 | 4.12
62 | test
63 |
64 |
65 | org.mockito
66 | mockito-core
67 | 2.2.0
68 | test
69 |
70 |
71 | ch.qos.logback
72 | logback-classic
73 | 1.2.0
74 | test
75 |
76 |
77 |
78 |
79 |
80 | org.apache.maven.plugins
81 | maven-compiler-plugin
82 | 3.7.0
83 |
84 | 1.8
85 | 1.8
86 |
87 |
88 |
89 | org.apache.maven.plugins
90 | maven-release-plugin
91 | 2.5.3
92 |
93 | true
94 | false
95 | release
96 | deploy
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | release
105 |
106 |
107 |
108 | org.apache.maven.plugins
109 | maven-source-plugin
110 | 3.2.1
111 |
112 |
113 | attach-sources
114 |
115 | jar-no-fork
116 |
117 |
118 |
119 |
120 |
121 | org.apache.maven.plugins
122 | maven-javadoc-plugin
123 | 3.3.0
124 |
125 |
126 | attach-javadocs
127 |
128 | jar
129 |
130 |
131 |
132 |
133 |
134 | org.apache.maven.plugins
135 | maven-gpg-plugin
136 | 3.0.1
137 |
138 |
139 | sign-artifacts
140 | verify
141 |
142 | sign
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/src/main/java/com/adamlewis/guice/persist/jooq/JdbcLocalTxnInterceptor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Adam L. Lewis
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.adamlewis.guice.persist.jooq;
18 |
19 | import java.lang.reflect.Method;
20 | import java.util.concurrent.ConcurrentHashMap;
21 | import java.util.concurrent.ConcurrentMap;
22 |
23 | import com.google.inject.Inject;
24 | import com.google.inject.Provider;
25 | import com.google.inject.persist.Transactional;
26 | import com.google.inject.persist.UnitOfWork;
27 | import org.aopalliance.intercept.MethodInterceptor;
28 | import org.aopalliance.intercept.MethodInvocation;
29 | import org.jooq.impl.DefaultConnectionProvider;
30 | import org.slf4j.Logger;
31 | import org.slf4j.LoggerFactory;
32 |
33 | /**
34 | * @author Adam Lewis
35 | */
36 | class JdbcLocalTxnInterceptor implements MethodInterceptor {
37 | private static final Logger logger = LoggerFactory.getLogger(JdbcLocalTxnInterceptor.class);
38 | private static final ConcurrentMap methodsTransactionals = new ConcurrentHashMap();
39 |
40 | private final Provider jooqPersistServiceProvider;
41 | private final Provider unitOfWorkProvider;
42 |
43 | @Transactional
44 | private static class Internal {
45 | }
46 |
47 | // Tracks if the unit of work was begun implicitly by this transaction.
48 | private final ThreadLocal didWeStartWork = new ThreadLocal();
49 |
50 | @Inject
51 | public JdbcLocalTxnInterceptor(Provider jooqPersistServiceProvider,
52 | Provider unitOfWorkProvider) {
53 | this.jooqPersistServiceProvider = jooqPersistServiceProvider;
54 | this.unitOfWorkProvider = unitOfWorkProvider;
55 | }
56 |
57 | public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
58 | UnitOfWork unitOfWork = unitOfWorkProvider.get();
59 | JooqPersistService jooqProvider = jooqPersistServiceProvider.get();
60 |
61 | // Should we start a unit of work?
62 | if (!jooqProvider.isWorking()) {
63 | unitOfWork.begin();
64 | didWeStartWork.set(true);
65 | }
66 |
67 | Transactional transactional = readTransactionMetadata(methodInvocation);
68 | DefaultConnectionProvider conn = jooqProvider.getConnectionWrapper();
69 |
70 | // Allow 'joining' of transactions if there is an enclosing @Transactional method.
71 | if (!conn.getAutoCommit()) {
72 | return methodInvocation.proceed();
73 | }
74 |
75 | logger.debug("Disabling JDBC auto commit for this thread");
76 | conn.setAutoCommit(false);
77 |
78 | Object result;
79 |
80 | try {
81 | result = methodInvocation.proceed();
82 | } catch (Exception e) {
83 | //commit transaction only if rollback didn't occur
84 | if (rollbackIfNecessary(transactional, e, conn)) {
85 | logger.debug("Committing JDBC transaction");
86 | conn.commit();
87 | }
88 |
89 | logger.debug("Enabling auto commit for this thread");
90 | conn.setAutoCommit(true);
91 |
92 | //propagate whatever exception is thrown anyway
93 | throw e;
94 | } finally {
95 | // Close the em if necessary (guarded so this code doesn't run unless catch fired).
96 | if (null != didWeStartWork.get() && conn.getAutoCommit()) {
97 | didWeStartWork.remove();
98 | unitOfWork.end();
99 | }
100 | }
101 |
102 | // everything was normal so commit the txn (do not move into try block above as it
103 | // interferes with the advised method's throwing semantics)
104 | try {
105 | logger.debug("Committing JDBC transaction");
106 | conn.commit();
107 | logger.debug("Enabling auto commit for this thread");
108 | conn.setAutoCommit(true);
109 | } finally {
110 | //close the em if necessary
111 | if (null != didWeStartWork.get()) {
112 | didWeStartWork.remove();
113 | unitOfWork.end();
114 | }
115 | }
116 |
117 | //or return result
118 | return result;
119 | }
120 |
121 | private Transactional readTransactionMetadata(final MethodInvocation methodInvocation) {
122 | Method method = methodInvocation.getMethod();
123 | Transactional cachedTransactional = methodsTransactionals.get(method);
124 | if (cachedTransactional != null) {
125 | return cachedTransactional;
126 | }
127 |
128 | Transactional transactional = method.getAnnotation(Transactional.class);
129 | if (null == transactional) {
130 | // If none on method, try the class.
131 | Class> targetClass = methodInvocation.getThis().getClass();
132 | transactional = targetClass.getAnnotation(Transactional.class);
133 | }
134 |
135 | if (null != transactional) {
136 | methodsTransactionals.put(method, transactional);
137 | } else {
138 | // If there is no transactional annotation present, use the default
139 | transactional = Internal.class.getAnnotation(Transactional.class);
140 | }
141 |
142 | return transactional;
143 | }
144 |
145 | /**
146 | * Returns True if rollback DID NOT HAPPEN (i.e. if commit should continue).
147 | *
148 | * @param transactional The metadata annotation of the method
149 | * @param e The exception to test for rollback
150 | * @param txn A JPA Transaction to issue rollbacks on
151 | */
152 | private boolean rollbackIfNecessary(final Transactional transactional,
153 | final Exception e,
154 | final DefaultConnectionProvider conn) {
155 | boolean commit = true;
156 |
157 | //check rollback clauses
158 | for (Class extends Exception> rollBackOn : transactional.rollbackOn()) {
159 |
160 | //if one matched, try to perform a rollback
161 | if (rollBackOn.isInstance(e)) {
162 | commit = false;
163 |
164 | //check ignore clauses (supercedes rollback clause)
165 | for (Class extends Exception> exceptOn : transactional.ignore()) {
166 | //An exception to the rollback clause was found, DON'T rollback
167 | // (i.e. commit and throw anyway)
168 | if (exceptOn.isInstance(e)) {
169 | commit = true;
170 | break;
171 | }
172 | }
173 |
174 | //rollback only if nothing matched the ignore check
175 | if (!commit) {
176 | logger.debug("Rolling back JDBC transaction for this thread");
177 | conn.rollback();
178 | }
179 | //otherwise continue to commit
180 |
181 | break;
182 | }
183 | }
184 |
185 | return commit;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/main/java/com/adamlewis/guice/persist/jooq/JooqPersistModule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Adam L. Lewis
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.adamlewis.guice.persist.jooq;
18 |
19 | import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder;
20 |
21 | import com.google.inject.Singleton;
22 | import com.google.inject.persist.PersistModule;
23 | import com.google.inject.persist.PersistService;
24 | import com.google.inject.persist.UnitOfWork;
25 | import org.aopalliance.intercept.MethodInterceptor;
26 | import org.jooq.Configuration;
27 | import org.jooq.DSLContext;
28 | import org.jooq.conf.Settings;
29 |
30 | /**
31 | * Jooq Factory provider for guice persist.
32 | *
33 | * @author Adam Lewis
34 | */
35 | public final class JooqPersistModule extends PersistModule {
36 | private MethodInterceptor transactionInterceptor;
37 |
38 | @Override
39 | protected void configurePersistence() {
40 | newOptionalBinder(binder(), Settings.class);
41 | newOptionalBinder(binder(), Configuration.class);
42 | bind(JooqPersistService.class).in(Singleton.class);
43 | bind(PersistService.class).to(JooqPersistService.class);
44 | bind(UnitOfWork.class).to(JooqPersistService.class);
45 | bind(DSLContext.class).toProvider(JooqPersistService.class);
46 |
47 | transactionInterceptor = new JdbcLocalTxnInterceptor(getProvider(JooqPersistService.class),
48 | getProvider(UnitOfWork.class));
49 | requestInjection(transactionInterceptor);
50 | }
51 |
52 | @Override
53 | protected MethodInterceptor getTransactionInterceptor() {
54 | return transactionInterceptor;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/adamlewis/guice/persist/jooq/JooqPersistService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Adam L. Lewis
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.adamlewis.guice.persist.jooq;
18 |
19 | import java.util.Optional;
20 | import javax.sql.DataSource;
21 |
22 | import java.sql.Connection;
23 | import java.sql.SQLException;
24 |
25 | import com.google.inject.Inject;
26 | import com.google.inject.Provider;
27 | import com.google.inject.Singleton;
28 | import com.google.inject.persist.PersistService;
29 | import com.google.inject.persist.UnitOfWork;
30 | import org.jooq.Configuration;
31 | import org.jooq.DSLContext;
32 | import org.jooq.SQLDialect;
33 | import org.jooq.conf.Settings;
34 | import org.jooq.impl.DSL;
35 | import org.jooq.impl.DefaultConnectionProvider;
36 | import org.slf4j.Logger;
37 | import org.slf4j.LoggerFactory;
38 |
39 |
40 | /**
41 | * Based on the JPA Persistence Service by Dhanji R. Prasanna (dhanji@gmail.com)
42 | *
43 | * @author Adam Lewis (github@adamlewis.com)
44 | */
45 | @Singleton
46 | class JooqPersistService implements Provider, UnitOfWork, PersistService {
47 |
48 | private static final Logger logger = LoggerFactory.getLogger(JooqPersistService.class);
49 |
50 | private final ThreadLocal threadFactory = new ThreadLocal();
51 | private final ThreadLocal threadConnection = new ThreadLocal();
52 | private final Provider jdbcSource;
53 | private final SQLDialect sqlDialect;
54 | private final Settings jooqSettings;
55 | private final Configuration configuration;
56 |
57 | @Inject
58 | public JooqPersistService(final Provider jdbcSource, final SQLDialect sqlDialect,
59 | Optional jooqSettings, Optional configuration) {
60 | this.jdbcSource = jdbcSource;
61 | this.sqlDialect = sqlDialect;
62 | this.configuration = configuration.orElse(null);
63 | if (configuration.isPresent() && jooqSettings.isPresent()) {
64 | logger.warn("@Injected org.jooq.conf.Settings is being ignored since a full org.jooq.Configuration was supplied");
65 | this.jooqSettings = null;
66 | } else {
67 | this.jooqSettings = jooqSettings.orElse(null);
68 | }
69 | }
70 |
71 | public DSLContext get() {
72 | DSLContext factory = threadFactory.get();
73 | if(null == factory) {
74 | throw new IllegalStateException("Requested Factory outside work unit. "
75 | + "Try calling UnitOfWork.begin() first, use @Transactional annotation"
76 | + "or use a PersistFilter if you are inside a servlet environment.");
77 | }
78 |
79 | return factory;
80 | }
81 |
82 | public DefaultConnectionProvider getConnectionWrapper() {
83 | return threadConnection.get();
84 | }
85 |
86 | public boolean isWorking() {
87 | return threadFactory.get() != null;
88 | }
89 |
90 | public void begin() {
91 | if(null != threadFactory.get()) {
92 | throw new IllegalStateException("Work already begun on this thread. "
93 | + "It looks like you have called UnitOfWork.begin() twice"
94 | + " without a balancing call to end() in between.");
95 | }
96 |
97 | DefaultConnectionProvider conn;
98 | try {
99 | logger.debug("Getting JDBC connection");
100 | DataSource dataSource = jdbcSource.get();
101 | Connection jdbcConn = dataSource.getConnection();
102 | conn = new DefaultConnectionProvider(jdbcConn);
103 | } catch (SQLException e) {
104 | throw new RuntimeException(e);
105 | }
106 |
107 | DSLContext jooqFactory;
108 |
109 | if (configuration != null) {
110 | logger.debug("Creating factory from configuration having dialect {}", configuration.dialect());
111 | jooqFactory = DSL.using(conn, configuration.dialect(), configuration.settings());
112 | } else {
113 | if (jooqSettings == null) {
114 | logger.debug("Creating factory with dialect {}", sqlDialect);
115 | jooqFactory = DSL.using(conn, sqlDialect);
116 | } else {
117 | logger.debug("Creating factory with dialect {} and settings.", sqlDialect);
118 | jooqFactory = DSL.using(conn, sqlDialect, jooqSettings);
119 | }
120 | }
121 | threadConnection.set(conn);
122 | threadFactory.set(jooqFactory);
123 | }
124 |
125 | public void end() {
126 | DSLContext jooqFactory = threadFactory.get();
127 | DefaultConnectionProvider conn = threadConnection.get();
128 | // Let's not penalize users for calling end() multiple times.
129 | if (null == jooqFactory) {
130 | return;
131 | }
132 |
133 | try {
134 | logger.debug("Closing JDBC connection");
135 | conn.acquire().close();
136 | } catch (SQLException e) {
137 | throw new RuntimeException(e);
138 | }
139 | threadFactory.remove();
140 | threadConnection.remove();
141 | }
142 |
143 |
144 | public synchronized void start() {
145 | //nothing to do on start
146 | }
147 |
148 | public synchronized void stop() {
149 | //nothing to do on stop
150 | }
151 |
152 |
153 | }
154 |
--------------------------------------------------------------------------------
/src/test/java/com/adamlewis/guice/persist/jooq/JdbcLocalTxnInterceptorTest.java:
--------------------------------------------------------------------------------
1 | package com.adamlewis.guice.persist.jooq;
2 |
3 | import java.lang.reflect.Method;
4 |
5 | import com.adamlewis.guice.persist.jooq.utils.MockConnection;
6 | import com.adamlewis.guice.persist.jooq.utils.Providers;
7 | import com.google.inject.persist.Transactional;
8 | import com.google.inject.persist.UnitOfWork;
9 | import org.aopalliance.intercept.MethodInvocation;
10 | import org.jooq.impl.DefaultConnectionProvider;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.mockito.Mock;
15 | import org.mockito.runners.MockitoJUnitRunner;
16 |
17 | import static org.junit.Assert.*;
18 | import static org.mockito.Mockito.*;
19 |
20 | @RunWith(MockitoJUnitRunner.class)
21 | public class JdbcLocalTxnInterceptorTest {
22 | @Mock
23 | private JooqPersistService jooqPersistService;
24 | @Mock
25 | private UnitOfWork unitOfWork;
26 | @Mock
27 | private MockConnection connection;
28 | @Mock
29 | private MethodInvocation methodInvocation;
30 |
31 | private JdbcLocalTxnInterceptor interceptor;
32 |
33 | @Transactional
34 | public void transaction() {
35 | }
36 |
37 | @Before
38 | public void setUp() throws Exception {
39 | interceptor = new JdbcLocalTxnInterceptor(Providers.of(jooqPersistService), Providers.of(unitOfWork));
40 |
41 | when(connection.getAutoCommit()).thenCallRealMethod();
42 | doCallRealMethod().when(connection).setAutoCommit(anyBoolean());
43 | connection.setAutoCommit(true);
44 |
45 | DefaultConnectionProvider connectionProvider = new DefaultConnectionProvider(connection);
46 | when(jooqPersistService.getConnectionWrapper()).thenReturn(connectionProvider);
47 | when(jooqPersistService.isWorking()).thenReturn(false);
48 |
49 | // Method is final. Mockito doesn't support mocking final classes. Using reflection
50 | Method defaultTransaction = JdbcLocalTxnInterceptorTest.class.getMethod("transaction");
51 | when(methodInvocation.getMethod()).thenReturn(defaultTransaction);
52 | }
53 |
54 | @Test
55 | public void unitOfWorkEnds() throws Throwable {
56 | interceptor.invoke(methodInvocation);
57 |
58 | verify(unitOfWork).begin();
59 | verify(connection).commit();
60 | verify(unitOfWork).end();
61 | }
62 |
63 | @Test
64 | public void unitOfWorkEndsOnException() throws Throwable {
65 | when(methodInvocation.proceed()).thenThrow(Exception.class);
66 |
67 | try {
68 | interceptor.invoke(methodInvocation);
69 | fail("exception expected");
70 | } catch (Exception ignored) {
71 | }
72 |
73 | verify(unitOfWork).begin();
74 | verify(connection).commit();
75 | verify(unitOfWork).end();
76 | }
77 |
78 | @Test
79 | public void unitOfWorkEndsOnRollbackException() throws Throwable {
80 | when(methodInvocation.proceed()).thenThrow(RuntimeException.class);
81 |
82 | try {
83 | interceptor.invoke(methodInvocation);
84 | fail("exception expected");
85 | } catch (RuntimeException ignored) {
86 | }
87 |
88 | verify(unitOfWork).begin();
89 | verify(connection).rollback();
90 | verify(unitOfWork).end();
91 | }
92 | }
--------------------------------------------------------------------------------
/src/test/java/com/adamlewis/guice/persist/jooq/JooqPersistServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.adamlewis.guice.persist.jooq;
2 |
3 | import java.sql.Connection;
4 | import java.util.*;
5 |
6 | import com.adamlewis.guice.persist.jooq.modules.ConfigurationModule;
7 | import com.adamlewis.guice.persist.jooq.modules.DataSourceModule;
8 | import com.adamlewis.guice.persist.jooq.modules.SettingsModule;
9 | import com.google.inject.Guice;
10 | import com.google.inject.Injector;
11 | import com.google.inject.Module;
12 | import javax.sql.DataSource;
13 | import org.jooq.Configuration;
14 | import org.jooq.conf.BackslashEscaping;
15 | import org.junit.Before;
16 | import org.junit.Test;
17 |
18 | import static org.junit.Assert.*;
19 | import static org.mockito.Mockito.mock;
20 | import static org.mockito.Mockito.when;
21 |
22 | public class JooqPersistServiceTest {
23 |
24 | private Injector injector;
25 |
26 | @Before
27 | public void setup() {
28 | injector = null;
29 | }
30 |
31 | @Test
32 | public void canCreateWithoutConfiguration() {
33 | JooqPersistService jooqPersistService = givenJooqPersistServiceWithModule();
34 | jooqPersistService.begin();
35 |
36 | assertEquals(DataSourceModule.DEFAULT_DIALECT, jooqPersistService.get().configuration().dialect());
37 | }
38 |
39 | @Test
40 | public void canProvideAConfiguration() throws Exception {
41 | JooqPersistService jooqPersistService = givenJooqPersistServiceWithModule(new ConfigurationModule());
42 | DataSource dataSource = injector.getInstance(DataSource.class);
43 | Connection connectionMock = mock(Connection.class);
44 | when(dataSource.getConnection()).thenReturn(connectionMock);
45 |
46 | jooqPersistService.begin();
47 |
48 | Configuration configuration = injector.getInstance(Configuration.class);
49 | Configuration transactionConfiguration = jooqPersistService.get().configuration();
50 | assertNotNull(transactionConfiguration);
51 | // the connection must be provided for each transaction configuration hence it will differ from default configuration
52 | assertNotEquals(configuration, transactionConfiguration);
53 | assertEquals(configuration.dialect(), transactionConfiguration.dialect());
54 | assertEquals(configuration.settings(), transactionConfiguration.settings());
55 | assertEquals(connectionMock, transactionConfiguration.connectionProvider().acquire());
56 | }
57 |
58 | @Test
59 | public void canProvideSettings() {
60 | JooqPersistService jooqPersistService = givenJooqPersistServiceWithModule(new SettingsModule());
61 | jooqPersistService.begin();
62 |
63 | // We can't assert on Settings.equals() because jooq clones the Settings instance and Settings does not override equals().
64 | assertEquals(SettingsModule.ESCAPING, jooqPersistService.get().settings().getBackslashEscaping());
65 | }
66 |
67 | @Test
68 | public void canProvideSettingsAndConfigurationButSettingsIsIgnored() {
69 | JooqPersistService jooqPersistService = givenJooqPersistServiceWithModule(new ConfigurationModule(), new SettingsModule());
70 | jooqPersistService.begin();
71 |
72 | Configuration configuration = injector.getInstance(Configuration.class);
73 | Configuration transactionConfiguration = jooqPersistService.get().configuration();
74 | assertNotNull(transactionConfiguration);
75 | assertNotEquals(configuration, transactionConfiguration);
76 | assertEquals(configuration.dialect(), transactionConfiguration.dialect());
77 | assertEquals(configuration.settings(), transactionConfiguration.settings());
78 | assertEquals(BackslashEscaping.DEFAULT, transactionConfiguration.settings().getBackslashEscaping());
79 | }
80 |
81 | @Test(expected = IllegalStateException.class)
82 | public void throwsIfUnitOfWorkIsNotStarted() {
83 | JooqPersistService jooqPersistService = givenJooqPersistServiceWithModule();
84 |
85 | jooqPersistService.get();
86 | }
87 |
88 | private JooqPersistService givenJooqPersistServiceWithModule(Module... modules) {
89 | Set moduleList = new HashSet<>(Arrays.asList(modules));
90 | moduleList.add(new JooqPersistModule());
91 | moduleList.add(new DataSourceModule());
92 | injector = Guice.createInjector(moduleList);
93 | return injector.getInstance(JooqPersistService.class);
94 | }
95 | }
--------------------------------------------------------------------------------
/src/test/java/com/adamlewis/guice/persist/jooq/modules/ConfigurationModule.java:
--------------------------------------------------------------------------------
1 | package com.adamlewis.guice.persist.jooq.modules;
2 |
3 | import com.google.inject.AbstractModule;
4 | import org.jooq.Configuration;
5 | import org.jooq.SQLDialect;
6 | import org.jooq.impl.DefaultConfiguration;
7 |
8 | public class ConfigurationModule extends AbstractModule {
9 | public static final SQLDialect DIALECT = SQLDialect.CUBRID;
10 |
11 | protected void configure() {
12 | DefaultConfiguration configuration = new DefaultConfiguration();
13 | configuration.setSQLDialect(DIALECT);
14 | binder().bind(Configuration.class).toInstance(configuration);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/test/java/com/adamlewis/guice/persist/jooq/modules/DataSourceModule.java:
--------------------------------------------------------------------------------
1 | package com.adamlewis.guice.persist.jooq.modules;
2 |
3 | import com.google.inject.AbstractModule;
4 | import com.google.inject.Provides;
5 | import org.jooq.SQLDialect;
6 |
7 | import javax.sql.DataSource;
8 |
9 | import static org.mockito.Mockito.*;
10 |
11 | public class DataSourceModule extends AbstractModule {
12 | public static final SQLDialect DEFAULT_DIALECT = SQLDialect.SQLITE;
13 | private static final DataSource DATA_SOURCE = mock(DataSource.class);
14 |
15 | protected void configure() {
16 | binder().requireExplicitBindings();
17 | bind(SQLDialect.class).toInstance(DEFAULT_DIALECT);
18 | }
19 |
20 | @Provides
21 | public DataSource mockDataSource(){
22 | return DATA_SOURCE;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/com/adamlewis/guice/persist/jooq/modules/SettingsModule.java:
--------------------------------------------------------------------------------
1 | package com.adamlewis.guice.persist.jooq.modules;
2 |
3 | import com.google.inject.AbstractModule;
4 | import org.jooq.conf.BackslashEscaping;
5 | import org.jooq.conf.Settings;
6 |
7 | public class SettingsModule extends AbstractModule {
8 | public static final BackslashEscaping ESCAPING = BackslashEscaping.OFF;
9 |
10 | protected void configure() {
11 | Settings settings= new Settings();
12 | settings.setBackslashEscaping(ESCAPING);
13 | binder().bind(Settings.class).toInstance(settings);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/java/com/adamlewis/guice/persist/jooq/utils/MockConnection.java:
--------------------------------------------------------------------------------
1 | package com.adamlewis.guice.persist.jooq.utils;
2 |
3 | import java.sql.SQLException;
4 |
5 | public class MockConnection extends org.jooq.tools.jdbc.MockConnection {
6 | private boolean autoCommit;
7 |
8 | public MockConnection() {
9 | super(null);
10 | }
11 |
12 | @Override
13 | public boolean getAutoCommit() throws SQLException {
14 | return autoCommit;
15 | }
16 |
17 | @Override
18 | public void setAutoCommit(boolean autoCommit) throws SQLException {
19 | this.autoCommit = autoCommit;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/test/java/com/adamlewis/guice/persist/jooq/utils/Providers.java:
--------------------------------------------------------------------------------
1 | package com.adamlewis.guice.persist.jooq.utils;
2 |
3 | import com.google.inject.Provider;
4 |
5 | public class Providers {
6 | private Providers() {
7 | }
8 |
9 | public static Provider of(T value) {
10 | return new SimpleProvider(value);
11 | }
12 |
13 | private static class SimpleProvider implements Provider {
14 | private T value;
15 |
16 | public SimpleProvider(T value) {
17 | this.value = value;
18 | }
19 |
20 | public T get() {
21 | return value;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------