88import org .hibernate .LockMode ;
99import org .hibernate .LockOptions ;
1010import org .hibernate .Locking ;
11- import org .hibernate .Timeouts ;
1211import org .hibernate .dialect .Dialect ;
1312import org .hibernate .dialect .RowLockStrategy ;
1413import org .hibernate .internal .util .collections .CollectionHelper ;
1716import org .hibernate .metamodel .mapping .PluralAttributeMapping ;
1817import org .hibernate .metamodel .mapping .internal .BasicValuedCollectionPart ;
1918import org .hibernate .persister .entity .EntityPersister ;
19+ import org .hibernate .persister .entity .JoinedSubclassEntityPersister ;
20+ import org .hibernate .sql .ast .SqlAstJoinType ;
2021import org .hibernate .sql .ast .spi .ForUpdateClauseStrategy ;
2122import org .hibernate .sql .ast .spi .SqlAppender ;
2223import org .hibernate .sql .ast .tree .from .TableGroup ;
24+ import org .hibernate .sql .ast .tree .from .TableGroupJoin ;
2325import org .hibernate .sql .ast .tree .from .TableReferenceJoin ;
2426import org .hibernate .sql .ast .tree .select .QuerySpec ;
2527
2628import java .util .ArrayList ;
2729import java .util .Collections ;
30+ import java .util .HashSet ;
2831import java .util .LinkedHashSet ;
2932import java .util .List ;
3033import java .util .Set ;
@@ -41,7 +44,8 @@ public class StandardForUpdateClauseStrategy implements ForUpdateClauseStrategy
4144 private final Locking .Scope lockingScope ;
4245 private final int timeout ;
4346
44- private Set <TableGroup > tableGroupsToLock ;
47+ private Set <TableGroup > rootsToLock ;
48+ private Set <TableGroupJoin > joinsToLock ;
4549
4650 public StandardForUpdateClauseStrategy (
4751 Dialect dialect ,
@@ -61,114 +65,136 @@ public StandardForUpdateClauseStrategy(
6165 }
6266
6367 @ Override
64- public void register (TableGroup tableGroup , boolean isRoot ) {
68+ public void registerRoot (TableGroup root ) {
6569 if ( rowLockStrategy == RowLockStrategy .NONE ) {
6670 // no need to collect these
6771 return ;
6872 }
6973
70- if ( isRoot ) {
71- assert tableGroup .isRealTableGroup ();
74+ if ( rootsToLock == null ) {
75+ rootsToLock = new HashSet <>();
76+ }
77+ rootsToLock .add ( root );
78+ }
7279
73- // we always want to lock tables which are part of the roots
74- trackTableGroup ( tableGroup );
80+ @ Override
81+ public void registerJoin (TableGroupJoin join ) {
82+ if ( rowLockStrategy == RowLockStrategy .NONE ) {
83+ // no need to collect these
84+ return ;
7585 }
7686 else if ( lockingScope == Locking .Scope .INCLUDE_COLLECTIONS ) {
7787 // if the TableGroup is an owned (aka, non-inverse) collection,
7888 // and we are to lock collections, track it
79- if ( tableGroup .getModelPart () instanceof PluralAttributeMapping attrMapping ) {
89+ if ( join . getJoinedGroup () .getModelPart () instanceof PluralAttributeMapping attrMapping ) {
8090 if ( !attrMapping .getCollectionDescriptor ().isInverse () ) {
8191 // owned collection
8292 if ( attrMapping .getElementDescriptor () instanceof BasicValuedCollectionPart ) {
8393 // an element-collection
84- trackTableGroup ( tableGroup );
94+ trackJoin ( join );
8595 }
8696 }
8797 }
8898 }
8999 else if ( lockingScope == Locking .Scope .INCLUDE_FETCHES ) {
90- if ( tableGroup .isFetched () ) {
91- trackTableGroup ( tableGroup );
100+ if ( join . getJoinedGroup () .isFetched () ) {
101+ trackJoin ( join );
92102 }
93103 }
94104 }
95105
96- private void trackTableGroup ( TableGroup tableGroup ) {
97- if ( tableGroupsToLock == null ) {
98- tableGroupsToLock = new LinkedHashSet <>();
106+ private void trackJoin ( TableGroupJoin join ) {
107+ if ( joinsToLock == null ) {
108+ joinsToLock = new LinkedHashSet <>();
99109 }
100- tableGroupsToLock .add ( tableGroup );
110+ joinsToLock .add ( join );
101111 }
102112
113+ @ Override
114+ public boolean containsOuterJoins () {
115+ for ( TableGroup tableGroup : rootsToLock ) {
116+ if ( tableGroup .getModelPart () instanceof JoinedSubclassEntityPersister ) {
117+ // inherently has outer joins
118+ return true ;
119+ }
120+ }
121+
122+ if ( joinsToLock == null ) {
123+ return false ;
124+ }
125+ for ( TableGroupJoin tableGroupJoin : joinsToLock ) {
126+ final TableGroup joinedGroup = tableGroupJoin .getJoinedGroup ();
127+ if ( tableGroupJoin .isInitialized ()
128+ && tableGroupJoin .getJoinType () != SqlAstJoinType .INNER
129+ && !joinedGroup .isVirtual () ) {
130+ return true ;
131+ }
132+ if ( joinedGroup .getModelPart () instanceof JoinedSubclassEntityPersister ) {
133+ // inherently has outer joins
134+ return true ;
135+ }
136+ }
137+
138+ return false ;
139+ }
103140
104141 @ Override
105142 public void render (SqlAppender sqlAppender ) {
106- renderLockTerm ( dialect , lockKind , timeout , sqlAppender );
107- renderRowLocking ( rowLockStrategy , tableGroupsToLock , sqlAppender );
143+ renderLockFragment ( dialect , lockKind , timeout , rowLockStrategy , sqlAppender );
108144 renderResultSetOptions ( dialect , sqlAppender );
109- renderLockedRowHandling ( timeout , sqlAppender );
110145 }
111146
112- protected void renderLockTerm (
147+ protected void renderLockFragment (
113148 Dialect dialect ,
114149 PessimisticLockKind lockKind ,
115150 int timeout ,
151+ RowLockStrategy rowLockStrategy ,
116152 SqlAppender sqlAppender ) {
117- final String term = lockKind == PessimisticLockKind .SHARE
118- ? dialect .getReadLockString ( timeout )
119- : dialect .getWriteLockString ( timeout );
120- sqlAppender .append ( term );
121- }
122-
123- protected void renderRowLocking (RowLockStrategy rowLockStrategy , Set <TableGroup > tableGroupsToLock , SqlAppender sqlAppender ) {
153+ final String fragment ;
124154 if ( rowLockStrategy == RowLockStrategy .NONE ) {
125- return ;
155+ fragment = lockKind == PessimisticLockKind .SHARE
156+ ? dialect .getReadLockString ( timeout )
157+ : dialect .getWriteLockString ( timeout );
126158 }
159+ else {
160+ final String lockItemsFragment = collectLockItems ();
161+ fragment = lockKind == PessimisticLockKind .SHARE
162+ ? dialect .getReadLockString ( lockItemsFragment , timeout )
163+ : dialect .getWriteLockString ( lockItemsFragment , timeout );
164+ }
165+ sqlAppender .append ( fragment );
166+ }
127167
128- assert tableGroupsToLock != null && !tableGroupsToLock .isEmpty ();
129-
130- sqlAppender .append ( " of " );
131-
168+ private String collectLockItems () {
132169 final List <String > lockItems = new ArrayList <>();
133- for ( TableGroup tableGroup : tableGroupsToLock ) {
134- collectLockItems ( tableGroup , lockItems );
170+ for ( TableGroup root : rootsToLock ) {
171+ collectLockItems ( root , lockItems );
172+ }
173+ if ( joinsToLock != null ) {
174+ for ( TableGroupJoin join : joinsToLock ) {
175+ collectLockItems ( join .getJoinedGroup (), lockItems );
176+ }
135177 }
136178
179+ final StringBuilder buffer = new StringBuilder ();
137180 boolean first = true ;
138181 for ( String lockItem : lockItems ) {
139182 if ( first ) {
140183 first = false ;
141184 }
142185 else {
143- sqlAppender . appendSql ( ',' );
186+ buffer . append ( ',' );
144187 }
145- sqlAppender . appendSql ( lockItem );
188+ buffer . append ( lockItem );
146189 }
190+
191+ return buffer .toString ();
147192 }
148193
149194 protected void renderResultSetOptions (Dialect dialect , SqlAppender sqlAppender ) {
150195 // hook for Derby
151196 }
152197
153- protected void renderLockedRowHandling (int timeout , SqlAppender sqlAppender ) {
154- if ( timeout == Timeouts .NO_WAIT_MILLI ) {
155- if ( dialect .supportsNoWait () ) {
156- sqlAppender .append ( dialect .getForUpdateNowaitString () );
157- }
158- }
159- else if ( timeout == Timeouts .SKIP_LOCKED_MILLI ) {
160- if ( dialect .supportsSkipLocked () ) {
161- sqlAppender .append ( dialect .getForUpdateSkipLockedString () );
162- }
163- }
164- else if ( timeout > 0 ) {
165- if ( dialect .supportsLockTimeouts () ) {
166- sqlAppender .append ( " wait " );
167- sqlAppender .append ( Integer .toString ( Timeouts .getTimeoutInSeconds ( timeout ) ) );
168- }
169- }
170- }
171-
172198 private void collectLockItems (TableGroup tableGroup , List <String > lockItems ) {
173199 if ( rowLockStrategy == RowLockStrategy .TABLE ) {
174200 addTableAliases ( tableGroup , lockItems );
0 commit comments