Skip to content

Commit 0ada8cc

Browse files
authored
Merge pull request #3573 from jooby-project/3572
hibernate: add StatelessSession support
2 parents 18260bd + c864afc commit 0ada8cc

File tree

6 files changed

+202
-69
lines changed

6 files changed

+202
-69
lines changed

modules/jooby-hibernate/src/main/java/io/jooby/hibernate/HibernateModule.java

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,23 @@
55
*/
66
package io.jooby.hibernate;
77

8-
import java.net.URL;
9-
import java.util.Arrays;
108
import java.util.Collections;
119
import java.util.HashMap;
1210
import java.util.List;
13-
import java.util.Map;
1411
import java.util.Objects;
1512
import java.util.Optional;
1613
import java.util.stream.Collectors;
1714
import java.util.stream.Stream;
1815

1916
import javax.sql.DataSource;
2017

21-
import org.hibernate.Session;
22-
import org.hibernate.SessionBuilder;
23-
import org.hibernate.SessionFactory;
24-
import org.hibernate.boot.Metadata;
25-
import org.hibernate.boot.MetadataBuilder;
18+
import org.hibernate.*;
2619
import org.hibernate.boot.MetadataSources;
27-
import org.hibernate.boot.SessionFactoryBuilder;
28-
import org.hibernate.boot.registry.BootstrapServiceRegistry;
2920
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
3021
import org.hibernate.boot.registry.StandardServiceRegistry;
3122
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
3223
import org.hibernate.cfg.AvailableSettings;
3324

34-
import com.typesafe.config.Config;
3525
import edu.umd.cs.findbugs.annotations.NonNull;
3626
import io.jooby.Environment;
3727
import io.jooby.Extension;
@@ -40,6 +30,7 @@
4030
import io.jooby.ServiceRegistry;
4131
import io.jooby.internal.hibernate.ScanEnvImpl;
4232
import io.jooby.internal.hibernate.SessionServiceProvider;
33+
import io.jooby.internal.hibernate.StatelessSessionServiceProvider;
4334
import io.jooby.internal.hibernate.UnitOfWorkProvider;
4435
import jakarta.inject.Provider;
4536
import jakarta.persistence.EntityManager;
@@ -145,19 +136,21 @@ public class HibernateModule implements Extension {
145136

146137
private final String name;
147138
private List<String> packages = Collections.emptyList();
148-
private List<Class> classes;
139+
private final List<Class<?>> classes;
149140
private HibernateConfigurer configurer = new HibernateConfigurer();
150141
private SessionProvider sessionBuilder = SessionBuilder::openSession;
142+
private StatelessSessionProvider statelessSessionProvider =
143+
StatelessSessionBuilder::openStatelessSession;
151144

152145
/**
153146
* Creates a Hibernate module.
154147
*
155148
* @param name The name/key of the data source to attach.
156149
* @param classes Persistent classes.
157150
*/
158-
public HibernateModule(@NonNull String name, Class... classes) {
151+
public HibernateModule(@NonNull String name, Class<?>... classes) {
159152
this.name = name;
160-
this.classes = Arrays.asList(classes);
153+
this.classes = List.of(classes);
161154
}
162155

163156
/**
@@ -176,7 +169,7 @@ public HibernateModule(Class... classes) {
176169
* @param name The name/key of the data source to attach.
177170
* @param classes Persistent classes.
178171
*/
179-
public HibernateModule(@NonNull String name, List<Class> classes) {
172+
public HibernateModule(@NonNull String name, List<Class<?>> classes) {
180173
this.name = name;
181174
this.classes = classes;
182175
}
@@ -188,7 +181,7 @@ public HibernateModule(@NonNull String name, List<Class> classes) {
188181
* @return This module.
189182
*/
190183
public @NonNull HibernateModule scan(@NonNull String... packages) {
191-
this.packages = Arrays.asList(packages);
184+
this.packages = List.of(packages);
192185
return this;
193186
}
194187

@@ -227,36 +220,36 @@ public HibernateModule(@NonNull String name, List<Class> classes) {
227220

228221
@Override
229222
public void install(@NonNull Jooby application) {
230-
Environment env = application.getEnvironment();
231-
Config config = application.getConfig();
232-
ServiceRegistry registry = application.getServices();
233-
DataSource dataSource = registry.getOrNull(ServiceKey.key(DataSource.class, name));
223+
var env = application.getEnvironment();
224+
var config = application.getConfig();
225+
var registry = application.getServices();
226+
var dataSource = registry.getOrNull(ServiceKey.key(DataSource.class, name));
234227
boolean fallback = false;
235228
if (dataSource == null) {
236229
// TODO: replace with usage exception
237230
dataSource = registry.require(DataSource.class);
238231
fallback = true;
239232
}
240-
BootstrapServiceRegistryBuilder bsrb = new BootstrapServiceRegistryBuilder();
233+
var bsrb = new BootstrapServiceRegistryBuilder();
241234
boolean defaultDdlAuto = env.isActive("dev", "test");
242235

243-
boolean flyway = isFlywayPresent(env, registry, name, fallback);
244-
String ddlAuto = flyway ? "none" : (defaultDdlAuto ? "update" : "none");
236+
var flyway = isFlywayPresent(env, registry, name, fallback);
237+
var ddlAuto = flyway ? "none" : (defaultDdlAuto ? "update" : "none");
245238

246239
configurer.configure(bsrb, config);
247240

248-
BootstrapServiceRegistry bsr = bsrb.build();
249-
StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder(bsr);
241+
var bsr = bsrb.build();
242+
var ssrb = new StandardServiceRegistryBuilder(bsr);
250243

251244
ssrb.applySetting(AvailableSettings.HBM2DDL_AUTO, ddlAuto);
252245
ssrb.applySetting(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, "managed");
253246
// apply application.conf
254247
var base = env.getProperties("hibernate");
255248
var custom = env.getProperties(name + ".hibernate", "hibernate");
256-
Map<String, String> javax = env.getProperties("hibernate.javax", "javax");
257-
Map<String, String> jakarta = env.getProperties("hibernate.jakarta", "jakarta");
249+
var javax = env.getProperties("hibernate.javax", "javax");
250+
var jakarta = env.getProperties("hibernate.jakarta", "jakarta");
258251

259-
Map<String, Object> settings = new HashMap<>();
252+
var settings = new HashMap<String, Object>();
260253
settings.putAll(base);
261254
settings.putAll(custom);
262255
settings.putAll(javax);
@@ -282,50 +275,59 @@ public void install(@NonNull Jooby application) {
282275

283276
configurer.configure(sources, config);
284277

285-
/** Scan package? */
286-
ClassLoader classLoader = env.getClassLoader();
287-
List<URL> packages =
278+
/* Scan package? */
279+
var classLoader = env.getClassLoader();
280+
var packages =
288281
sources.getAnnotatedPackages().stream()
289282
.map(pkg -> classLoader.getResource(pkg.replace('.', '/')))
290-
.collect(Collectors.toList());
283+
.toList();
291284

292-
MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
285+
var metadataBuilder = sources.getMetadataBuilder();
293286
if (!packages.isEmpty()) {
294287
metadataBuilder.applyScanEnvironment(new ScanEnvImpl(packages));
295288
}
296289

297290
configurer.configure(metadataBuilder, config);
298291

299-
Metadata metadata = metadataBuilder.build();
292+
var metadata = metadataBuilder.build();
300293

301-
SessionFactoryBuilder sfb = metadata.getSessionFactoryBuilder();
294+
var sfb = metadata.getSessionFactoryBuilder();
302295
sfb.applyName(name);
303296
sfb.applyNameAsJndiName(false);
304297

305298
configurer.configure(sfb, config);
306299

307-
SessionFactory sf = sfb.build();
300+
var sf = sfb.build();
308301

309-
/** Session and EntityManager. */
302+
/* Session and EntityManager. */
310303
Provider sessionServiceProvider = new SessionServiceProvider(sf, sessionBuilder);
311304
registry.putIfAbsent(Session.class, sessionServiceProvider);
312305
registry.put(ServiceKey.key(Session.class, name), sessionServiceProvider);
313306

314307
registry.putIfAbsent(EntityManager.class, sessionServiceProvider);
315308
registry.put(ServiceKey.key(EntityManager.class, name), sessionServiceProvider);
316309

317-
/** SessionFactory and EntityManagerFactory. */
310+
/* StatelessSession. */
311+
registry.putIfAbsent(
312+
StatelessSession.class, new StatelessSessionServiceProvider(sf, statelessSessionProvider));
313+
314+
/* SessionFactory and EntityManagerFactory. */
318315
registry.putIfAbsent(SessionFactory.class, sf);
319316
registry.put(ServiceKey.key(SessionFactory.class, name), sf);
320317

321318
registry.putIfAbsent(EntityManagerFactory.class, sf);
322319
registry.put(ServiceKey.key(EntityManagerFactory.class, name), sf);
323320

324-
/** Session Provider: */
321+
/* Session Provider: */
325322
registry.putIfAbsent(SessionProvider.class, sessionBuilder);
326323
registry.put(ServiceKey.key(SessionProvider.class, name), sessionBuilder);
327324

328-
/** UnitOfWork Provider: */
325+
/* StatelessSession Provider: */
326+
registry.putIfAbsent(StatelessSessionProvider.class, this.statelessSessionProvider);
327+
registry.put(
328+
ServiceKey.key(StatelessSessionProvider.class, name), this.statelessSessionProvider);
329+
330+
/* UnitOfWork Provider: */
329331
UnitOfWorkProvider unitOfWorkProvider = new UnitOfWorkProvider(sf, sessionBuilder);
330332
registry.putIfAbsent(UnitOfWork.class, unitOfWorkProvider);
331333
registry.put(ServiceKey.key(UnitOfWork.class, name), unitOfWorkProvider);

modules/jooby-hibernate/src/main/java/io/jooby/hibernate/SessionRequest.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
import org.hibernate.Session;
99
import org.hibernate.SessionFactory;
1010
import org.hibernate.Transaction;
11-
import org.hibernate.context.internal.ManagedSessionContext;
1211
import org.hibernate.resource.transaction.spi.TransactionStatus;
1312
import org.slf4j.Logger;
1413
import org.slf4j.LoggerFactory;
1514

1615
import edu.umd.cs.findbugs.annotations.NonNull;
1716
import io.jooby.Route;
1817
import io.jooby.ServiceKey;
18+
import io.jooby.internal.hibernate.RequestSessionFactory;
1919

2020
/**
2121
* Attach {@link Session} and {@link jakarta.persistence.EntityManager} to the current request.
@@ -52,11 +52,11 @@
5252
*/
5353
public class SessionRequest implements Route.Filter {
5454

55-
private static final Logger log = LoggerFactory.getLogger(SessionRequest.class);
55+
private final Logger log = LoggerFactory.getLogger(getClass());
5656

5757
private final ServiceKey<SessionFactory> sessionFactoryKey;
5858

59-
private final ServiceKey<SessionProvider> sessionProviderKey;
59+
private RequestSessionFactory sessionProvider;
6060

6161
/**
6262
* Creates a new session request and attach the to a named session factory.
@@ -74,17 +74,16 @@ public SessionRequest() {
7474

7575
private SessionRequest(ServiceKey<SessionFactory> sessionFactoryKey) {
7676
this.sessionFactoryKey = sessionFactoryKey;
77-
this.sessionProviderKey = ServiceKey.key(SessionProvider.class, sessionFactoryKey.getName());
77+
this.sessionProvider =
78+
RequestSessionFactory.stateful(
79+
ServiceKey.key(SessionProvider.class, sessionFactoryKey.getName()));
7880
}
7981

8082
@NonNull @Override
8183
public Route.Handler apply(@NonNull Route.Handler next) {
8284
return ctx -> {
83-
SessionFactory sessionFactory = ctx.require(sessionFactoryKey);
84-
SessionProvider sessionProvider = ctx.require(sessionProviderKey);
85-
Session session = sessionProvider.newSession(sessionFactory.withOptions());
86-
try {
87-
ManagedSessionContext.bind(session);
85+
var sessionFactory = ctx.require(sessionFactoryKey);
86+
try (var session = sessionProvider.create(ctx, sessionFactory)) {
8887

8988
Object result = next.apply(ctx);
9089

@@ -99,10 +98,7 @@ public Route.Handler apply(@NonNull Route.Handler next) {
9998

10099
return result;
101100
} finally {
102-
ManagedSessionContext.unbind(sessionFactory);
103-
if (session != null) {
104-
session.close();
105-
}
101+
sessionProvider.release(sessionFactory);
106102
}
107103
};
108104
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.hibernate;
7+
8+
import org.hibernate.StatelessSession;
9+
import org.hibernate.StatelessSessionBuilder;
10+
11+
import edu.umd.cs.findbugs.annotations.NonNull;
12+
13+
/**
14+
* Allow to customize a Session before opening it.
15+
*
16+
* @author edgar
17+
* @since 2.5.1
18+
*/
19+
public interface StatelessSessionProvider {
20+
/**
21+
* Creates a new session.
22+
*
23+
* @param builder Session builder.
24+
* @return A new session.
25+
*/
26+
@NonNull StatelessSession newSession(@NonNull StatelessSessionBuilder builder);
27+
}

modules/jooby-hibernate/src/main/java/io/jooby/hibernate/TransactionalRequest.java

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,14 @@
88
import org.hibernate.Session;
99
import org.hibernate.SessionFactory;
1010
import org.hibernate.Transaction;
11-
import org.hibernate.context.internal.ManagedSessionContext;
1211
import org.hibernate.resource.transaction.spi.TransactionStatus;
1312
import org.slf4j.Logger;
1413
import org.slf4j.LoggerFactory;
1514

1615
import edu.umd.cs.findbugs.annotations.NonNull;
17-
import io.jooby.Route;
18-
import io.jooby.ServiceKey;
19-
import io.jooby.SneakyThrows;
16+
import io.jooby.*;
2017
import io.jooby.annotation.Transactional;
18+
import io.jooby.internal.hibernate.RequestSessionFactory;
2119

2220
/**
2321
* Attaches a {@link Session} and {@link jakarta.persistence.EntityManager} to the current request
@@ -52,11 +50,11 @@
5250
*/
5351
public class TransactionalRequest implements Route.Filter {
5452

55-
private static final Logger log = LoggerFactory.getLogger(SessionRequest.class);
53+
private final Logger log = LoggerFactory.getLogger(getClass());
5654

5755
private final ServiceKey<SessionFactory> sessionFactoryKey;
5856

59-
private final ServiceKey<SessionProvider> sessionProviderKey;
57+
private RequestSessionFactory sessionProvider;
6058

6159
private boolean enabledByDefault = true;
6260

@@ -78,10 +76,9 @@ public TransactionalRequest() {
7876

7977
private TransactionalRequest(ServiceKey<SessionFactory> sessionFactoryKey) {
8078
this.sessionFactoryKey = sessionFactoryKey;
81-
this.sessionProviderKey =
82-
sessionFactoryKey.getName() == null
83-
? ServiceKey.key(SessionProvider.class)
84-
: ServiceKey.key(SessionProvider.class, sessionFactoryKey.getName());
79+
this.sessionProvider =
80+
RequestSessionFactory.stateful(
81+
ServiceKey.key(SessionProvider.class, sessionFactoryKey.getName()));
8582
}
8683

8784
/**
@@ -99,15 +96,24 @@ public TransactionalRequest enabledByDefault(boolean enabledByDefault) {
9996
return this;
10097
}
10198

99+
/**
100+
* Creates a {@link org.hibernate.StatelessSession} and attach to current HTTP request.
101+
*
102+
* @return This instance.
103+
*/
104+
public TransactionalRequest useStatelessSession() {
105+
this.sessionProvider =
106+
RequestSessionFactory.stateless(
107+
ServiceKey.key(StatelessSessionProvider.class, sessionFactoryKey.getName()));
108+
return this;
109+
}
110+
102111
@NonNull @Override
103112
public Route.Handler apply(@NonNull Route.Handler next) {
104113
return ctx -> {
105114
if (ctx.getRoute().isTransactional(enabledByDefault)) {
106-
SessionFactory sessionFactory = ctx.require(sessionFactoryKey);
107-
SessionProvider sessionProvider = ctx.require(sessionProviderKey);
108-
109-
try (Session session = sessionProvider.newSession(sessionFactory.withOptions())) {
110-
ManagedSessionContext.bind(session);
115+
var sessionFactory = ctx.require(sessionFactoryKey);
116+
try (var session = sessionProvider.create(ctx, sessionFactory)) {
111117

112118
Object result;
113119

@@ -132,7 +138,7 @@ public Route.Handler apply(@NonNull Route.Handler next) {
132138

133139
return result;
134140
} finally {
135-
ManagedSessionContext.unbind(sessionFactory);
141+
sessionProvider.release(sessionFactory);
136142
}
137143
} else {
138144
return next.apply(ctx);

0 commit comments

Comments
 (0)