Skip to content

Commit 179c1d1

Browse files
dreab8Sanne
authored andcommitted
HHH-4808 Add test for issue
1 parent 8f4450c commit 179c1d1

File tree

1 file changed

+365
-0
lines changed

1 file changed

+365
-0
lines changed
Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.test.connections;
8+
9+
import java.sql.Connection;
10+
import java.sql.DriverManager;
11+
import java.sql.SQLException;
12+
import java.util.HashSet;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.Properties;
16+
import java.util.Set;
17+
import javax.persistence.Entity;
18+
import javax.persistence.EntityManager;
19+
import javax.persistence.FetchType;
20+
import javax.persistence.Id;
21+
import javax.persistence.JoinColumn;
22+
import javax.persistence.ManyToOne;
23+
import javax.persistence.OneToMany;
24+
import javax.persistence.Query;
25+
import javax.sql.DataSource;
26+
27+
import org.hibernate.annotations.Fetch;
28+
import org.hibernate.annotations.FetchMode;
29+
import org.hibernate.annotations.LazyCollection;
30+
import org.hibernate.annotations.LazyCollectionOption;
31+
import org.hibernate.annotations.LazyToOne;
32+
import org.hibernate.annotations.LazyToOneOption;
33+
import org.hibernate.cfg.AvailableSettings;
34+
import org.hibernate.cfg.Environment;
35+
import org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl;
36+
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
37+
import org.hibernate.jpa.test.connection.BaseDataSource;
38+
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
39+
40+
import org.hibernate.testing.TestForIssue;
41+
import org.junit.Assert;
42+
import org.junit.Before;
43+
import org.junit.Test;
44+
45+
import static junit.framework.TestCase.assertTrue;
46+
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
47+
import static org.junit.Assert.assertEquals;
48+
import static org.mockito.Mockito.spy;
49+
50+
/**
51+
* @author Selaron
52+
*/
53+
@TestForIssue(jiraKey = "HHH-4808")
54+
public class LazyLoadingConnectionCloseTest extends BaseEntityManagerFunctionalTestCase {
55+
56+
private ConnectionProviderDecorator connectionProvider;
57+
58+
@Override
59+
protected Class<?>[] getAnnotatedClasses() {
60+
return new Class[] { SimpleEntity.class, ChildEntity.class };
61+
}
62+
63+
@Override
64+
protected Map getConfig() {
65+
Map config = super.getConfig();
66+
config.put( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" );
67+
68+
config.put( AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT );
69+
70+
config.put( AvailableSettings.AUTOCOMMIT, "false" );
71+
72+
connectionProvider = new ConnectionProviderDecorator( getDataSource() );
73+
config.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider );
74+
return config;
75+
}
76+
77+
@Before
78+
public void setUp() {
79+
doInJPA( this::entityManagerFactory, entityManager -> {
80+
final SimpleEntity entity = new SimpleEntity();
81+
entity.setId( 1L );
82+
entity.setName( "TheParent" );
83+
84+
final ChildEntity c1 = new ChildEntity();
85+
c1.setId( 1L );
86+
c1.setParent( entity );
87+
c1.setName( "child1" );
88+
89+
final ChildEntity c2 = new ChildEntity();
90+
c2.setId( 2L );
91+
c2.setParent( entity );
92+
c2.setName( "child2" );
93+
94+
entityManager.persist( entity );
95+
entityManager.persist( c1 );
96+
entityManager.persist( c2 );
97+
} );
98+
}
99+
100+
/**
101+
* Tests connections get closed after transaction commit.
102+
*/
103+
@Test
104+
public void testConnectionCloseAfterTx() {
105+
connectionProvider.clear();
106+
EntityManager entityManager = getOrCreateEntityManager();
107+
108+
try {
109+
entityManager.getTransaction().begin();
110+
try {
111+
112+
final Query qry = entityManager.createQuery( "FROM SimpleEntity" );
113+
final List<SimpleEntity> entities = qry.getResultList();
114+
final SimpleEntity entity = entities.get( 0 );
115+
assertEquals( 1, connectionProvider.getCurrentOpenConnections() );
116+
}
117+
catch (Exception e) {
118+
if ( entityManager.getTransaction().isActive() ) {
119+
entityManager.getTransaction().rollback();
120+
}
121+
throw e;
122+
}
123+
finally {
124+
if ( entityManager.getTransaction().isActive() ) {
125+
entityManager.getTransaction().commit();
126+
}
127+
}
128+
assertTrue( connectionProvider.areAllConnectionClosed() );
129+
}
130+
finally {
131+
entityManager.close();
132+
}
133+
134+
}
135+
136+
/**
137+
* Tests connections get closed after lazy collection initialization.
138+
*/
139+
@Test
140+
public void testConnectionCloseAfterLazyCollectionInit() {
141+
connectionProvider.clear();
142+
EntityManager entityManager = getOrCreateEntityManager();
143+
144+
try {
145+
final Query qry = entityManager.createQuery( "FROM SimpleEntity" );
146+
final List<SimpleEntity> entities = qry.getResultList();
147+
final SimpleEntity entity = entities.get( 0 );
148+
149+
// assert no connection is open
150+
assertTrue( connectionProvider.areAllConnectionClosed() );
151+
152+
final int oldOpenedConnections = connectionProvider.getTotalOpenedConnectionCount();
153+
final Set<ChildEntity> lazyChildren = entity.getChildren();
154+
155+
// this will initialize the collection and such trigger a query
156+
lazyChildren.stream().findAny();
157+
158+
// assert a connection had been opened
159+
Assert.assertTrue( oldOpenedConnections < connectionProvider.getTotalOpenedConnectionCount() );
160+
161+
// assert there's no remaining connection left.
162+
assertTrue( connectionProvider.areAllConnectionClosed() );
163+
164+
}
165+
finally {
166+
entityManager.close();
167+
}
168+
}
169+
170+
/**
171+
* Tests connections get closed after transaction commit.
172+
*/
173+
@Test
174+
public void testConnectionCloseAfterLazyPojoPropertyInit() {
175+
connectionProvider.clear();
176+
EntityManager entityManager = getOrCreateEntityManager();
177+
178+
try {
179+
final Query qry = entityManager.createQuery( "FROM ChildEntity" );
180+
final List<ChildEntity> entities = qry.getResultList();
181+
final ChildEntity entity = entities.get( 0 );
182+
183+
// assert no connection is open
184+
assertTrue( connectionProvider.areAllConnectionClosed() );
185+
186+
final int oldOpenedConnections = connectionProvider.getTotalOpenedConnectionCount();
187+
188+
final SimpleEntity parent = entity.getParent();
189+
// this will initialize the collection and such trigger a query
190+
parent.getName();
191+
// assert a connection had been opened
192+
Assert.assertTrue( oldOpenedConnections < connectionProvider.getTotalOpenedConnectionCount() );
193+
194+
195+
// assert there's no remaining connection left.
196+
assertTrue( connectionProvider.areAllConnectionClosed() );
197+
}
198+
finally {
199+
entityManager.close();
200+
}
201+
}
202+
203+
/**
204+
* Tests connections get closed after transaction commit.
205+
*/
206+
@Test
207+
public void testConnectionCloseAfterQueryWithoutTx() {
208+
connectionProvider.clear();
209+
EntityManager entityManager = getOrCreateEntityManager();
210+
211+
try {
212+
213+
final int oldOpenedConnections = connectionProvider.getTotalOpenedConnectionCount();
214+
final List<ChildEntity> childrenByQuery = entityManager.createQuery( "FROM ChildEntity" ).getResultList();
215+
assertTrue( childrenByQuery.size() > 0 );
216+
217+
// assert a connection had been opened
218+
assertTrue( oldOpenedConnections < connectionProvider.getTotalOpenedConnectionCount() );
219+
// assert there's no remaining connection left.
220+
assertTrue( connectionProvider.areAllConnectionClosed() );
221+
}
222+
finally {
223+
entityManager.close();
224+
}
225+
}
226+
227+
@Entity(name = "SimpleEntity")
228+
public static class SimpleEntity {
229+
private Long id;
230+
231+
private String name;
232+
233+
Set<ChildEntity> children = new HashSet<>();
234+
235+
@Id
236+
public Long getId() {
237+
return id;
238+
}
239+
240+
public void setId(final Long id) {
241+
this.id = id;
242+
}
243+
244+
public String getName() {
245+
return name;
246+
}
247+
248+
public void setName(final String name) {
249+
this.name = name;
250+
}
251+
252+
@OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent")
253+
@LazyCollection(LazyCollectionOption.EXTRA)
254+
@Fetch(FetchMode.SELECT)
255+
public Set<ChildEntity> getChildren() {
256+
return children;
257+
}
258+
259+
public void setChildren(final Set<ChildEntity> children) {
260+
this.children = children;
261+
}
262+
}
263+
264+
@Entity(name = "ChildEntity")
265+
public static class ChildEntity {
266+
private Long id;
267+
268+
private String name;
269+
270+
private SimpleEntity parent;
271+
272+
@Id
273+
public Long getId() {
274+
return id;
275+
}
276+
277+
public void setId(final Long id) {
278+
this.id = id;
279+
}
280+
281+
public String getName() {
282+
return name;
283+
}
284+
285+
public void setName(final String name) {
286+
this.name = name;
287+
}
288+
289+
@ManyToOne(fetch = FetchType.LAZY)
290+
@JoinColumn
291+
@LazyToOne(LazyToOneOption.PROXY)
292+
public SimpleEntity getParent() {
293+
return parent;
294+
}
295+
296+
public void setParent(final SimpleEntity parent) {
297+
this.parent = parent;
298+
}
299+
}
300+
301+
private BaseDataSource getDataSource() {
302+
final Properties connectionProps = new Properties();
303+
connectionProps.put( "user", Environment.getProperties().getProperty( Environment.USER ) );
304+
connectionProps.put( "password", Environment.getProperties().getProperty( Environment.PASS ) );
305+
306+
final String url = Environment.getProperties().getProperty( Environment.URL );
307+
return new BaseDataSource() {
308+
@Override
309+
public Connection getConnection() throws SQLException {
310+
return DriverManager.getConnection( url, connectionProps );
311+
}
312+
313+
@Override
314+
public Connection getConnection(String username, String password) throws SQLException {
315+
return DriverManager.getConnection( url, connectionProps );
316+
}
317+
};
318+
}
319+
320+
public static class ConnectionProviderDecorator extends UserSuppliedConnectionProviderImpl {
321+
322+
private final DataSource dataSource;
323+
324+
private int connectionCount;
325+
private int openConnections;
326+
327+
private Connection connection;
328+
329+
public ConnectionProviderDecorator(DataSource dataSource) {
330+
this.dataSource = dataSource;
331+
}
332+
333+
@Override
334+
public Connection getConnection() throws SQLException {
335+
connectionCount++;
336+
openConnections++;
337+
connection = spy( dataSource.getConnection() );
338+
return connection;
339+
}
340+
341+
@Override
342+
public void closeConnection(Connection connection) throws SQLException {
343+
connection.close();
344+
openConnections--;
345+
}
346+
347+
public int getTotalOpenedConnectionCount() {
348+
return this.connectionCount;
349+
}
350+
351+
public int getCurrentOpenConnections() {
352+
return openConnections;
353+
}
354+
355+
public boolean areAllConnectionClosed() {
356+
return openConnections == 0;
357+
}
358+
359+
public void clear() {
360+
connectionCount = 0;
361+
openConnections = 0;
362+
}
363+
}
364+
365+
}

0 commit comments

Comments
 (0)