Skip to content

Commit 537a1c0

Browse files
Fix cache build for honoring mapped concurrency
1 parent f31fe59 commit 537a1c0

File tree

11 files changed

+401
-88
lines changed

11 files changed

+401
-88
lines changed

src/NHibernate.Test/Async/CacheTest/GetQueryCacheFixture.cs renamed to src/NHibernate.Test/Async/CacheTest/BuildCacheFixture.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,28 @@
99

1010

1111
using System;
12-
using System.Collections;
1312
using System.Collections.Concurrent;
1413
using System.Collections.Generic;
1514
using System.Threading;
1615
using NHibernate.Cache;
1716
using NHibernate.Cfg;
17+
using NHibernate.Engine;
18+
using NHibernate.Util;
1819
using NUnit.Framework;
1920
using Environment = NHibernate.Cfg.Environment;
2021

2122
namespace NHibernate.Test.CacheTest
2223
{
2324
using System.Threading.Tasks;
2425
[TestFixture]
25-
public class GetQueryCacheFixtureAsync : TestCase
26+
public class BuildCacheFixtureAsync : TestCase
2627
{
27-
protected override string[] Mappings => new[] { "Simple.hbm.xml" };
28+
protected override string MappingsAssembly => "NHibernate.Test";
29+
30+
protected override string[] Mappings => new[] { "CacheTest.EntitiesInSameRegion.hbm.xml" };
31+
32+
// Disable the TestCase cache overrides.
33+
protected override string CacheConcurrencyStrategy => null;
2834

2935
protected override void Configure(Configuration configuration)
3036
{

src/NHibernate.Test/CacheTest/GetQueryCacheFixture.cs renamed to src/NHibernate.Test/CacheTest/BuildCacheFixture.cs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,87 @@
11
using System;
2-
using System.Collections;
32
using System.Collections.Concurrent;
43
using System.Collections.Generic;
54
using System.Threading;
65
using NHibernate.Cache;
76
using NHibernate.Cfg;
7+
using NHibernate.Engine;
8+
using NHibernate.Util;
89
using NUnit.Framework;
910
using Environment = NHibernate.Cfg.Environment;
1011

1112
namespace NHibernate.Test.CacheTest
1213
{
1314
[TestFixture]
14-
public class GetQueryCacheFixture : TestCase
15+
public class BuildCacheFixture : TestCase
1516
{
16-
protected override string[] Mappings => new[] { "Simple.hbm.xml" };
17+
protected override string MappingsAssembly => "NHibernate.Test";
18+
19+
protected override string[] Mappings => new[] { "CacheTest.EntitiesInSameRegion.hbm.xml" };
20+
21+
// Disable the TestCase cache overrides.
22+
protected override string CacheConcurrencyStrategy => null;
1723

1824
protected override void Configure(Configuration configuration)
1925
{
2026
configuration.SetProperty(Environment.UseQueryCache, "true");
2127
configuration.SetProperty(Environment.CacheProvider, typeof(LockedCacheProvider).AssemblyQualifiedName);
2228
}
2329

30+
[Theory]
31+
public void CommonRegionHasExpectedConcurrency(bool withPrefix)
32+
{
33+
const string prefix = "Prefix";
34+
const string region = "Common";
35+
var fullRegion = (withPrefix ? prefix + "." : "") + region;
36+
ISessionFactoryImplementor sfi = null;
37+
if (withPrefix)
38+
cfg.SetProperty(Environment.CacheRegionPrefix, prefix);
39+
try
40+
{
41+
sfi = withPrefix ? BuildSessionFactory() : Sfi;
42+
var commonRegionCache = sfi.GetSecondLevelCacheRegion(fullRegion);
43+
var entityAName = typeof(EntityA).FullName;
44+
var entityAConcurrencyCache = sfi.GetEntityPersister(entityAName).Cache;
45+
var entityACache = entityAConcurrencyCache.Cache;
46+
var entityBName = typeof(EntityB).FullName;
47+
var entityBConcurrencyCache = sfi.GetEntityPersister(entityBName).Cache;
48+
var entityBCache = entityBConcurrencyCache.Cache;
49+
var relatedAConcurrencyCache =
50+
sfi.GetCollectionPersister(StringHelper.Qualify(entityAName, nameof(EntityA.Related))).Cache;
51+
var relatedACache = relatedAConcurrencyCache.Cache;
52+
var relatedBConcurrencyCache =
53+
sfi.GetCollectionPersister(StringHelper.Qualify(entityBName, nameof(EntityB.Related))).Cache;
54+
var relatedBCache = relatedBConcurrencyCache.Cache;
55+
var queryCache = sfi.GetQueryCache(region).Cache;
56+
Assert.Multiple(
57+
() =>
58+
{
59+
Assert.That(commonRegionCache.RegionName, Is.EqualTo(fullRegion), "Unexpected region name for common region");
60+
Assert.That(entityACache.RegionName, Is.EqualTo(fullRegion), "Unexpected region name for EntityA");
61+
Assert.That(entityBCache.RegionName, Is.EqualTo(fullRegion), "Unexpected region name for EntityB");
62+
Assert.That(relatedACache.RegionName, Is.EqualTo(fullRegion), "Unexpected region name for RelatedA");
63+
Assert.That(relatedBCache.RegionName, Is.EqualTo(fullRegion), "Unexpected region name for RelatedB");
64+
Assert.That(queryCache.RegionName, Is.EqualTo(fullRegion), "Unexpected region name for query cache");
65+
});
66+
Assert.Multiple(
67+
() =>
68+
{
69+
Assert.That(entityAConcurrencyCache, Is.InstanceOf<ReadWriteCache>(), "Unexpected concurrency for EntityA");
70+
Assert.That(relatedAConcurrencyCache, Is.InstanceOf<NonstrictReadWriteCache>(), "Unexpected concurrency for RelatedA");
71+
Assert.That(entityBConcurrencyCache, Is.InstanceOf<ReadOnlyCache>(), "Unexpected concurrency for EntityB");
72+
Assert.That(relatedBConcurrencyCache, Is.InstanceOf<ReadWriteCache>(), "Unexpected concurrency for RelatedB");
73+
});
74+
}
75+
finally
76+
{
77+
if (withPrefix)
78+
{
79+
cfg.Properties.Remove(Environment.CacheRegionPrefix);
80+
sfi?.Dispose();
81+
}
82+
}
83+
}
84+
2485
[Test]
2586
public void RetrievedQueryCacheMatchesGloballyStoredOne()
2687
{
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Collections.Generic;
2+
3+
namespace NHibernate.Test.CacheTest
4+
{
5+
public class EntityInSameRegion
6+
{
7+
public virtual int Id { get; set; }
8+
public virtual string Description { get; set; }
9+
public virtual int Value { get; set; }
10+
public virtual ISet<EntityInSameRegion> Related { get; set; }
11+
}
12+
13+
public class EntityA : EntityInSameRegion
14+
{
15+
}
16+
17+
public class EntityB : EntityInSameRegion
18+
{
19+
}
20+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3+
assembly="NHibernate.Test"
4+
namespace="NHibernate.Test.CacheTest">
5+
<class name="EntityA">
6+
<cache usage="read-write" region="Common" />
7+
<id type="int" />
8+
<property name="Description"/>
9+
<property name="Value" column="`Value`"/>
10+
<set name="Related">
11+
<cache usage="nonstrict-read-write" region="Common" />
12+
<key column="Related" />
13+
<one-to-many class="EntityA"/>
14+
</set>
15+
</class>
16+
<class name="EntityB">
17+
<cache usage="read-only" region="Common" />
18+
<id type="int" />
19+
<property name="Description"/>
20+
<property name="Value" column="`Value`"/>
21+
<set name="Related">
22+
<cache usage="read-write" region="Common" />
23+
<key column="Related" />
24+
<one-to-many class="EntityB"/>
25+
</set>
26+
</class>
27+
<query name="EntityA.All" cache-region="Common" cacheable="true">
28+
from EntityA
29+
</query>
30+
</hibernate-mapping>

src/NHibernate/Async/Impl/SessionFactoryImpl.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ public sealed partial class SessionFactoryImpl : ISessionFactoryImplementor, IOb
8585

8686
if (settings.IsQueryCacheEnabled)
8787
{
88-
queryCache.Destroy();
89-
9088
foreach (var cache in queryCaches.Values)
9189
{
9290
cache.Value.Destroy();

src/NHibernate/Cache/CacheFactory.cs

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
using System;
22
using NHibernate.Cfg;
33
using System.Collections.Generic;
44

@@ -30,13 +30,44 @@ public static class CacheFactory
3030
/// <param name="settings">Used to retrieve the global cache region prefix.</param>
3131
/// <param name="properties">Properties the cache provider can use to configure the cache.</param>
3232
/// <returns>An <see cref="ICacheConcurrencyStrategy"/> to use for this object in the <see cref="ICache"/>.</returns>
33-
public static ICacheConcurrencyStrategy CreateCache(string usage, string name, bool mutable, Settings settings,
34-
IDictionary<string,string> properties)
33+
// Since v5.2
34+
[Obsolete("Please use overload with a CacheBase builder parameter.")]
35+
public static ICacheConcurrencyStrategy CreateCache(
36+
string usage,
37+
string name,
38+
bool mutable,
39+
Settings settings,
40+
IDictionary<string, string> properties)
3541
{
36-
if (usage == null || !settings.IsSecondLevelCacheEnabled) return null; //no cache
42+
var cache = CreateCache(
43+
usage, name, settings,
44+
(r, u) =>
45+
{
46+
var c = settings.CacheProvider.BuildCache(r, properties);
47+
return c as CacheBase ?? new ObsoleteCacheWrapper(c);
48+
});
3749

38-
string prefix = settings.CacheRegionPrefix;
39-
if (prefix != null) name = prefix + '.' + name;
50+
if (cache != null && mutable && usage == ReadOnly)
51+
log.Warn("read-only cache configured for mutable: {0}", name);
52+
53+
return cache;
54+
}
55+
56+
/// <summary>
57+
/// Creates an <see cref="ICacheConcurrencyStrategy"/> from the parameters.
58+
/// </summary>
59+
/// <param name="usage">The name of the strategy that <see cref="ICacheProvider"/> should use for the class.</param>
60+
/// <param name="name">The name of the cache region the strategy is being created for.</param>
61+
/// <param name="settings">Used to retrieve the global cache region prefix.</param>
62+
/// <param name="regionAndUsageCacheGetter">The delegate for obtaining the <see cref="ICache" /> to use for the region.</param>
63+
/// <returns>An <see cref="ICacheConcurrencyStrategy"/> to use for this object in the <see cref="ICache"/>.</returns>
64+
public static ICacheConcurrencyStrategy CreateCache(
65+
string usage,
66+
string name,
67+
Settings settings,
68+
Func<string, string, CacheBase> regionAndUsageCacheGetter)
69+
{
70+
if (usage == null || !settings.IsSecondLevelCacheEnabled) return null; //no cache
4071

4172
if (log.IsDebugEnabled())
4273
{
@@ -47,10 +78,6 @@ public static ICacheConcurrencyStrategy CreateCache(string usage, string name, b
4778
switch (usage)
4879
{
4980
case ReadOnly:
50-
if (mutable)
51-
{
52-
log.Warn("read-only cache configured for mutable: {0}", name);
53-
}
5481
ccs = new ReadOnlyCache();
5582
break;
5683
case ReadWrite:
@@ -59,17 +86,17 @@ public static ICacheConcurrencyStrategy CreateCache(string usage, string name, b
5986
case NonstrictReadWrite:
6087
ccs = new NonstrictReadWriteCache();
6188
break;
62-
//case CacheFactory.Transactional:
63-
// ccs = new TransactionalCache();
64-
// break;
89+
//case CacheFactory.Transactional:
90+
// ccs = new TransactionalCache();
91+
// break;
6592
default:
6693
throw new MappingException(
67-
"cache usage attribute should be read-write, read-only, nonstrict-read-write, or transactional");
94+
"cache usage attribute should be read-write, read-only or nonstrict-read-write");
6895
}
6996

7097
try
7198
{
72-
ccs.Cache = settings.CacheProvider.BuildCache(name, properties);
99+
ccs.Cache = regionAndUsageCacheGetter(name, usage);
73100
}
74101
catch (CacheException e)
75102
{

src/NHibernate/Cache/IQueryCacheFactory.cs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using NHibernate.Cfg;
34

@@ -9,7 +10,59 @@ namespace NHibernate.Cache
910
/// </summary>
1011
public interface IQueryCacheFactory
1112
{
12-
IQueryCache GetQueryCache(string regionName, UpdateTimestampsCache updateTimestampsCache, Settings settings,
13-
IDictionary<string, string> props);
13+
// Since v5.2
14+
[Obsolete("Please use extension overload with a CacheBase parameter.")]
15+
IQueryCache GetQueryCache(
16+
string regionName,
17+
UpdateTimestampsCache updateTimestampsCache,
18+
Settings settings,
19+
IDictionary<string, string> props);
1420
}
15-
}
21+
22+
// 6.0 TODO: move to interface.
23+
public static class QueryCacheFactoryExtension
24+
{
25+
private static readonly INHibernateLogger Logger = NHibernateLogger.For(typeof(QueryCacheFactoryExtension));
26+
27+
/// <summary>
28+
/// Build a query cache.
29+
/// </summary>
30+
/// <param name="factory">The query cache factory.</param>
31+
/// <param name="updateTimestampsCache">The cache of updates timestamps.</param>
32+
/// <param name="props">The NHibernate settings properties.</param>
33+
/// <param name="regionCache">The <see cref="ICache" /> to use for the region.</param>
34+
/// <returns>A query cache. <c>null</c> if <paramref name="factory"/> does not implement a
35+
/// <c>public IQueryCache GetQueryCache(UpdateTimestampsCache, IDictionary&lt;string, string&gt; props, CacheBase)</c>
36+
/// method.</returns>
37+
public static IQueryCache GetQueryCache(
38+
this IQueryCacheFactory factory,
39+
UpdateTimestampsCache updateTimestampsCache,
40+
IDictionary<string, string> props,
41+
CacheBase regionCache)
42+
{
43+
if (factory is StandardQueryCacheFactory standardFactory)
44+
{
45+
return standardFactory.GetQueryCache(updateTimestampsCache, props, regionCache);
46+
}
47+
48+
// Use reflection for supporting custom factories.
49+
var factoryType = factory.GetType();
50+
var getQueryCacheMethod = factoryType.GetMethod(
51+
nameof(StandardQueryCacheFactory.GetQueryCache),
52+
new[] { typeof(UpdateTimestampsCache), typeof(IDictionary<string, string>), typeof(CacheBase) });
53+
if (getQueryCacheMethod != null)
54+
{
55+
return (IQueryCache) getQueryCacheMethod.Invoke(
56+
factory,
57+
new object[] { updateTimestampsCache, props, regionCache });
58+
}
59+
60+
// Caller has to call the obsolete method.
61+
Logger.Warn(
62+
"{0} does not implement 'IQueryCache GetQueryCache(UpdateTimestampsCache, IDictionary&lt;string, " +
63+
"string&gt; props, CacheBase)', its obsolete overload will be used.",
64+
factoryType);
65+
return null;
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)