From f928923b5b380c1dafd261fac7c7cfaef2387f31 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 31 Oct 2018 23:53:16 +1300 Subject: [PATCH] #1519 - Memory leak when using @Cache(enableQueryCache=true) (#1523) * #1519 - Memory leak when using @Cache(enableQueryCache=true) * #1519 - Memory leak when using @Cache(enableQueryCache=true) Remove unused TransactionEventBeans --- .../ebeaninternal/api/TransactionEvent.java | 58 +++++++++----- .../api/TransactionEventBeans.java | 44 ---------- .../server/core/PersistRequestBean.java | 80 +++++++++++++++---- .../server/deploy/BeanCollectionUtil.java | 30 ++++++- .../server/deploy/BeanDescriptor.java | 1 - .../deploy/BeanDescriptorCacheHelp.java | 6 +- .../server/persist/DefaultPersister.java | 9 ++- .../server/transaction/JdbcTransaction.java | 5 +- .../transaction/PostCommitProcessing.java | 23 +++--- .../transaction/TransactionManager.java | 2 +- .../config/PlatformNoGeneratedKeysTest.java | 4 +- src/test/java/org/MainMemoryLeak.java | 76 ++++++++++++++++++ src/test/resources/ebean.properties | 4 + src/test/resources/logback-test.xml | 16 ++-- 14 files changed, 240 insertions(+), 118 deletions(-) delete mode 100644 src/main/java/io/ebeaninternal/api/TransactionEventBeans.java create mode 100644 src/test/java/org/MainMemoryLeak.java diff --git a/src/main/java/io/ebeaninternal/api/TransactionEvent.java b/src/main/java/io/ebeaninternal/api/TransactionEvent.java index d8b25c113..348dd2a0f 100644 --- a/src/main/java/io/ebeaninternal/api/TransactionEvent.java +++ b/src/main/java/io/ebeaninternal/api/TransactionEvent.java @@ -9,6 +9,7 @@ import io.ebeaninternal.server.transaction.TransactionManager; import io.ebeanservice.docstore.api.DocStoreUpdates; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; /** @@ -29,16 +30,21 @@ public class TransactionEvent implements Serializable { */ private final transient boolean local; + private final long startMillis; + private TransactionEventTable eventTables; - private transient TransactionEventBeans eventBeans; + private transient List> listenerNotify; private transient DeleteByIdMap deleteByIdMap; + private transient CacheChangeSet changeSet; + /** * Create the TransactionEvent, one per Transaction. */ - public TransactionEvent() { + public TransactionEvent(long startMillis) { + this.startMillis = startMillis; this.local = true; } @@ -71,8 +77,8 @@ public class TransactionEvent implements Serializable { /** * Return the list of PersistRequestBean's for this transaction. */ - public List> getPersistRequestBeans() { - return (eventBeans == null) ? null : eventBeans.getRequests(); + public List> getListenerNotify() { + return (listenerNotify == null) ? null : listenerNotify; } public TransactionEventTable getEventTables() { @@ -94,17 +100,13 @@ public class TransactionEvent implements Serializable { } /** - * Add a inserted updated or deleted bean to the event. + * Add post commit listeners. Watch this for large transactions. */ - public void add(PersistRequestBean request) { - - if (request.isNotify()) { - // either a BeanListener or Cache is interested - if (eventBeans == null) { - eventBeans = new TransactionEventBeans(); - } - eventBeans.add(request); + public void addListenerNotify(PersistRequestBean request) { + if (listenerNotify == null) { + listenerNotify = new ArrayList<>(); } + listenerNotify.add(request); } /** @@ -112,11 +114,13 @@ public class TransactionEvent implements Serializable { */ public CacheChangeSet buildCacheChanges(TransactionManager manager) { - if (eventBeans == null && deleteByIdMap == null && eventTables == null) { + if (changeSet == null && deleteByIdMap == null && eventTables == null) { return null; } - CacheChangeSet changeSet = new CacheChangeSet(manager.clockNowMillis()); + if (changeSet == null) { + changeSet = new CacheChangeSet(manager.clockNowMillis()); + } if (eventTables != null && !eventTables.isEmpty()) { // notify cache with table based changes BeanDescriptorManager dm = manager.getBeanDescriptorManager(); @@ -124,9 +128,6 @@ public class TransactionEvent implements Serializable { dm.cacheNotify(tableIUD, changeSet); } } - if (eventBeans != null) { - eventBeans.notifyCache(changeSet); - } if (deleteByIdMap != null) { deleteByIdMap.notifyCache(changeSet); } @@ -137,12 +138,25 @@ public class TransactionEvent implements Serializable { * Add any relevant PersistRequestBean's to DocStoreUpdates for later processing. */ public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) { - - List> persistRequestBeans = getPersistRequestBeans(); - if (persistRequestBeans != null) { - for (PersistRequestBean persistRequestBean : persistRequestBeans) { + List> requests = getListenerNotify(); + if (requests != null) { + for (PersistRequestBean persistRequestBean : requests) { persistRequestBean.addDocStoreUpdates(docStoreUpdates); } } } + + /** + * Return the CacheChangeSet that we add cache notification messages to. + * + * We want to add to this change set as we process requests allowing the + * PersistRequestBean to be garbage collected for large transactions. + */ + public CacheChangeSet obtainCacheChangeSet() { + if (changeSet == null) { + changeSet = new CacheChangeSet(startMillis); + } + return changeSet; + } + } diff --git a/src/main/java/io/ebeaninternal/api/TransactionEventBeans.java b/src/main/java/io/ebeaninternal/api/TransactionEventBeans.java deleted file mode 100644 index 8e0cd0621..000000000 --- a/src/main/java/io/ebeaninternal/api/TransactionEventBeans.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.ebeaninternal.api; - -import io.ebeaninternal.server.cache.CacheChangeSet; -import io.ebeaninternal.server.core.PersistRequestBean; - -import java.util.ArrayList; -import java.util.List; - -/** - * Lists of inserted updated and deleted beans that have a BeanPersistListener. - *

- * These beans will be sent to the appropriate BeanListeners after a successful - * commit of the transaction. - *

- */ -public class TransactionEventBeans { - - final ArrayList> requests = new ArrayList<>(); - - /** - * Return the list of PersistRequests that BeanListeners are interested in. - */ - public List> getRequests() { - return requests; - } - - /** - * Add a bean for BeanListener notification. - */ - public void add(PersistRequestBean request) { - - requests.add(request); - } - - /** - * Collect the cache changes. - */ - public void notifyCache(CacheChangeSet changeSet) { - for (PersistRequestBean request : requests) { - request.notifyCache(changeSet); - } - } - -} diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java index acaa7c57d..d2a93c9d6 100644 --- a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java @@ -173,6 +173,16 @@ public final class PersistRequestBean extends PersistRequest implements BeanP */ private int pendingPostUpdateNotify; + /** + * Set to true when post execute has occured (so includes batch flush). + */ + private boolean postExecute; + + /** + * Set to true after many properties have been persisted (so includes element collections). + */ + private boolean complete; + public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager mgr, SpiTransaction t, PersistExecute persistExecute, PersistRequest.Type type, int flags) { @@ -441,10 +451,10 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } /** - * Return true if this change should notify cache, listener or doc store. + * Return true if this change should notify persist listener or doc store (and keep the request). */ - public boolean isNotify() { - return notifyCache || isNotifyPersistListener() || isDocStoreNotify(); + private boolean isNotifyListeners() { + return isNotifyPersistListener() || isDocStoreNotify(); } /** @@ -918,7 +928,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP */ @Override public void postExecute() { - + postExecute = true; if (controller != null) { controllerPost(); } @@ -939,7 +949,8 @@ public final class PersistRequestBean extends PersistRequest implements BeanP postInsert(); } - addEvent(); + addPostCommitListeners(); + notifyCacheOnPostExecute(); if (isLogSummary()) { logSummary(); @@ -1017,14 +1028,12 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } /** - * Add the bean to the TransactionEvent. This will be used by TransactionManager to sync Cache, - * Cluster and text indexes. + * Add the request to TransactionEvent if there are post commit listeners. */ - private void addEvent() { - + private void addPostCommitListeners() { TransactionEvent event = transaction.getEvent(); - if (event != null) { - event.add(this); + if (event != null && isNotifyListeners()) { + event.addListenerNotify(this); } } @@ -1087,23 +1096,62 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } /** - * Check if any of its many properties where cascade saved and hence we need to update related - * many property caches. + * Completed insert or delete request. Do cache notify in non-batched case. */ - public void checkUpdatedManysOnly() { + public void complete() { + notifyCacheOnComplete(); + } + + /** + * Completed update request handling cases for element collection and where ONLY + * many properties were updated. + */ + public void completeUpdate() { if (!dirty && updatedManys != null) { // set the flag and register for post commit processing if there // is caching or registered listeners if (idValue == null) { this.idValue = beanDescriptor.getId(entityBean); } + // not dirty so postExecute() will never be called + // set true to trigger cache notify if needed + postExecute = true; updatedManysOnly = true; setNotifyCache(); - addEvent(); + addPostCommitListeners(); } + notifyCacheOnComplete(); postUpdateNotify(); } + /** + * Notify cache when using batched persist. + */ + private void notifyCacheOnPostExecute() { + postExecute = true; + if (notifyCache && complete) { + // add cache notification (on batch persist) + TransactionEvent event = transaction.getEvent(); + if (event != null) { + notifyCache(event.obtainCacheChangeSet()); + } + } + } + + /** + * Notify cache when not use batched persist. + */ + private void notifyCacheOnComplete() { + complete = true; + if (notifyCache && postExecute) { + // add cache notification (on non-batch persist) + TransactionEvent event = transaction.getEvent(); + if (event != null) { + notifyCache(event.obtainCacheChangeSet()); + } + } + } + /** * For requests that update document store add this event to either the list * of queue events or list of update events. @@ -1390,7 +1438,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP public void setImportedOrphanForRemoval(BeanPropertyAssocOne prop) { Object orphan = getOrigValue(prop); if (orphan instanceof EntityBean) { - orphanBean = (EntityBean)orphan; + orphanBean = (EntityBean) orphan; } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionUtil.java b/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionUtil.java index 0b8d46dcf..e632a729c 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionUtil.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionUtil.java @@ -22,7 +22,7 @@ public class BeanCollectionUtil { } /** - * Return the details of the collection or map taking care to avoid + * Return the details (map entry set) of the collection or map taking care to avoid * unnecessary fetching of the data. */ public static Collection getActualEntries(Object o) { @@ -48,4 +48,32 @@ public class BeanCollectionUtil { } throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]"); } + + /** + * Return the details (map values) of the collection or map taking care to avoid + * unnecessary fetching of the data. + */ + public static Collection getActualDetails(Object o) { + if (o == null) { + return null; + } + if (o instanceof BeanCollection) { + BeanCollection bc = (BeanCollection) o; + if (!bc.isPopulated()) { + return null; + } + // For maps this is a collection of Map.Entry, otherwise it + // returns a collection of beans + return bc.getActualDetails(); + } + + if (o instanceof Map) { + // yes, we want the entrySet (to set the keys) + return ((Map) o).values(); + + } else if (o instanceof Collection) { + return ((Collection) o); + } + throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]"); + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 6f951ab87..2ca824f98 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -7,7 +7,6 @@ import io.ebean.SqlUpdate; import io.ebean.Transaction; import io.ebean.ValuePair; import io.ebean.annotation.DocStoreMode; -import io.ebean.annotation.PartitionMode; import io.ebean.bean.BeanCollection; import io.ebean.bean.EntityBean; import io.ebean.bean.EntityBeanIntercept; diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java index ee602ab74..56f951b98 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java @@ -348,13 +348,12 @@ final class BeanDescriptorCacheHelp { private CachedManyIds createManyIds(BeanPropertyAssocMany many, Object details) { - BeanDescriptor targetDescriptor = many.getTargetDescriptor(); - - Collection actualDetails = BeanCollectionUtil.getActualEntries(details); + Collection actualDetails = BeanCollectionUtil.getActualDetails(details); if (actualDetails == null) { return null; } + BeanDescriptor targetDescriptor = many.getTargetDescriptor(); List idList = new ArrayList<>(actualDetails.size()); for (Object bean : actualDetails) { idList.add(targetDescriptor.getId((EntityBean) bean)); @@ -794,7 +793,6 @@ final class BeanDescriptorCacheHelp { // query caching only return; } - List> manyCollections = updateRequest.getUpdatedManyCollections(); if (manyCollections != null) { for (BeanPropertyAssocMany many : manyCollections) { diff --git a/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java index 2a1651660..8b2c6340a 100644 --- a/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java +++ b/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java @@ -412,7 +412,7 @@ public final class DefaultPersister implements Persister { if (req.isPersistCascade()) { saveAssocMany(req); } - req.checkUpdatedManysOnly(); + req.completeUpdate(); } else { update(req); } @@ -472,7 +472,7 @@ public final class DefaultPersister implements Persister { request.flagUpdate(); saveAssocMany(request); } - request.checkUpdatedManysOnly(); + request.completeUpdate(); } else { if (request.isInsert()) { insert(request); @@ -506,6 +506,8 @@ public final class DefaultPersister implements Persister { // save any associated List held beans saveAssocMany(request); } + request.complete(); + } finally { request.unRegisterBean(); } @@ -539,7 +541,7 @@ public final class DefaultPersister implements Persister { saveAssocMany(request); } - request.checkUpdatedManysOnly(); + request.completeUpdate(); } finally { request.unRegisterBean(); @@ -883,6 +885,7 @@ public final class DefaultPersister implements Persister { unloadedForeignKeys.deleteCascade(); } } + request.complete(); // return true if using JDBC batch (as we can't tell until the batch is flushed) return count; diff --git a/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java index bafd7a57f..2f469f3e9 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java @@ -14,7 +14,6 @@ import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.api.TransactionEvent; import io.ebeaninternal.api.TxnProfileEventCodes; import io.ebeaninternal.server.core.PersistDeferredRelationship; -import io.ebeaninternal.server.core.PersistRequest; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.lib.util.Str; import io.ebeaninternal.server.persist.BatchControl; @@ -855,7 +854,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes { public TransactionEvent getEvent() { queryOnly = false; if (event == null) { - event = new TransactionEvent(); + event = new TransactionEvent(startMillis); } return event; } @@ -1052,7 +1051,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes { // the event has been sent to the transaction manager // for postCommit processing (l2 cache updates etc) // start a new transaction event - event = new TransactionEvent(); + event = new TransactionEvent(startMillis); } catch (Exception e) { doRollback(e); diff --git a/src/main/java/io/ebeaninternal/server/transaction/PostCommitProcessing.java b/src/main/java/io/ebeaninternal/server/transaction/PostCommitProcessing.java index 550bb8be4..9797ef59f 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/PostCommitProcessing.java +++ b/src/main/java/io/ebeaninternal/server/transaction/PostCommitProcessing.java @@ -33,9 +33,7 @@ final class PostCommitProcessing { private final TransactionManager manager; - private final List> persistBeanRequests; - - private final BeanPersistIdMap beanPersistIdMap; + private final List> listenerNotify; private final RemoteTransactionEvent remoteTransactionEvent; @@ -57,8 +55,7 @@ final class PostCommitProcessing { this.txnDocStoreBatchSize = 0; this.event = event; this.deleteByIdMap = event.getDeleteByIdMap(); - this.persistBeanRequests = event.getPersistRequestBeans(); - this.beanPersistIdMap = createBeanPersistIdMap(); + this.listenerNotify = event.getListenerNotify(); this.remoteTransactionEvent = createRemoteTransactionEvent(); } @@ -74,8 +71,7 @@ final class PostCommitProcessing { this.txnDocStoreBatchSize = transaction.getDocStoreBatchSize(); this.event = transaction.getEvent(); this.deleteByIdMap = event.getDeleteByIdMap(); - this.persistBeanRequests = event.getPersistRequestBeans(); - this.beanPersistIdMap = createBeanPersistIdMap(); + this.listenerNotify = event.getListenerNotify(); this.remoteTransactionEvent = createRemoteTransactionEvent(); } @@ -159,9 +155,9 @@ final class PostCommitProcessing { } private void localPersistListenersNotify() { - if (persistBeanRequests != null) { - for (PersistRequestBean persistBeanRequest : persistBeanRequests) { - persistBeanRequest.notifyLocalPersistListener(); + if (listenerNotify != null) { + for (PersistRequestBean request : listenerNotify) { + request.notifyLocalPersistListener(); } } TransactionEventTable eventTables = event.getEventTables(); @@ -175,13 +171,13 @@ final class PostCommitProcessing { private BeanPersistIdMap createBeanPersistIdMap() { - if (persistBeanRequests == null) { + if (listenerNotify == null) { return null; } BeanPersistIdMap m = new BeanPersistIdMap(); - for (PersistRequestBean persistBeanRequest : persistBeanRequests) { - persistBeanRequest.addToPersistMap(m); + for (PersistRequestBean request : listenerNotify) { + request.addToPersistMap(m); } return m; } @@ -193,6 +189,7 @@ final class PostCommitProcessing { } RemoteTransactionEvent remoteTransactionEvent = new RemoteTransactionEvent(serverName); + BeanPersistIdMap beanPersistIdMap = createBeanPersistIdMap(); if (beanPersistIdMap != null) { for (BeanPersistIds beanPersist : beanPersistIdMap.values()) { remoteTransactionEvent.addBeanPersistIds(beanPersist); diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java index 51824492e..ee1ded434 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java @@ -452,7 +452,7 @@ public class TransactionManager implements SpiTransactionManager { private void externalModificationEvent(TransactionEventTable tableEvents) { - TransactionEvent event = new TransactionEvent(); + TransactionEvent event = new TransactionEvent(clockNowMillis()); event.add(tableEvents); PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, event); diff --git a/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java b/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java index 857577a9c..af873247e 100644 --- a/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java +++ b/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java @@ -93,9 +93,9 @@ public class PlatformNoGeneratedKeysTest { return EbeanServerFactory.create(config); } - static class OtherH2Platform extends H2Platform { + public static class OtherH2Platform extends H2Platform { - OtherH2Platform() { + public OtherH2Platform() { super(); this.platform = Platform.GENERIC; } diff --git a/src/test/java/org/MainMemoryLeak.java b/src/test/java/org/MainMemoryLeak.java new file mode 100644 index 000000000..03d00622d --- /dev/null +++ b/src/test/java/org/MainMemoryLeak.java @@ -0,0 +1,76 @@ +package org; + +import io.ebean.Ebean; +import io.ebean.EbeanServer; +import io.ebean.EbeanServerFactory; +import io.ebean.Transaction; +import io.ebean.annotation.Cache; +import io.ebean.config.PlatformNoGeneratedKeysTest; +import io.ebean.config.ServerConfig; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Lob; +import java.util.Arrays; + +/** + * + * Test preparation: + * + * - Enable file based H2 in ebean.properties (line `datasource.h2.databaseUrl=jdbc:h2:file:~/tests`) + * - disable logging of io.ebean.SUM/TXN/SQL in logback-test.xml + * - remove existing database in ~/tests (do that before each run) + * - run this class with `-Xmx1g` jvm argument + * - an OOM in iteration ~45000 will occur (45k * 10k chars = 45k * 20k bytes = 900MB) + * + * remove the line `@Cache(enableQueryCache=true)` - and it will run forever (until disk is full) + */ +public class MainMemoryLeak { + + @Cache(enableQueryCache=true) + @Entity + public static class ECachedBean { + @Id + private Long id; + + @Lob + private String description; + } + + public static void main(String[] args) { + + PlatformNoGeneratedKeysTest.OtherH2Platform platform = new PlatformNoGeneratedKeysTest.OtherH2Platform(); + + ServerConfig config = new ServerConfig(); + + config.setDatabasePlatform(platform); + config.addClass(ECachedBean.class); + config.loadFromProperties(); + + EbeanServer server = EbeanServerFactory.create(config); + + // create a string with 10k chars + char[] c = new char[10_000]; + Arrays.fill(c, 'x'); + + try (Transaction txn = server.beginTransaction()) { + for (int i = 0; i < 500000; i++) { + + if (i % 1000 == 0) { + long mem = Runtime.getRuntime().freeMemory() / 1024 / 1024; + if (mem > 1024) { + throw new IllegalStateException("-Xmx1g JVM argument expected"); + } + System.out.println("Iteration: " + i + " Free mem: " + mem + " MB"); + } + + ECachedBean a = new ECachedBean(); + a.description = new String(c); + server.save(a); + } + System.out.println("Success, no mem limit detected"); + txn.commit(); + } + + } +} diff --git a/src/test/resources/ebean.properties b/src/test/resources/ebean.properties index 0e9fc7c8a..d7a9263cc 100644 --- a/src/test/resources/ebean.properties +++ b/src/test/resources/ebean.properties @@ -56,6 +56,10 @@ ebean.migration.migrationPath=dbmigration/myapp ## ------------------------------------------------------------- +datasource.db.username=sa +datasource.db.password= +datasource.db.databaseUrl=jdbc:h2:file:~/tests +datasource.db.databaseDriver=org.h2.Driver datasource.h2.username=sa datasource.h2.password= diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index ce6598fd2..35a6bd233 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -82,16 +82,16 @@ - - - + + + - + - - - - + + + +