001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.orm.hibernate5; 018 019import java.io.Serializable; 020import java.lang.reflect.InvocationHandler; 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Method; 023import java.lang.reflect.Proxy; 024import java.util.Collection; 025import java.util.Iterator; 026import java.util.List; 027 028import javax.persistence.PersistenceException; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.hibernate.Criteria; 033import org.hibernate.Filter; 034import org.hibernate.FlushMode; 035import org.hibernate.Hibernate; 036import org.hibernate.HibernateException; 037import org.hibernate.LockMode; 038import org.hibernate.LockOptions; 039import org.hibernate.ReplicationMode; 040import org.hibernate.Session; 041import org.hibernate.SessionFactory; 042import org.hibernate.criterion.DetachedCriteria; 043import org.hibernate.criterion.Example; 044 045import org.springframework.beans.factory.InitializingBean; 046import org.springframework.dao.DataAccessException; 047import org.springframework.dao.InvalidDataAccessApiUsageException; 048import org.springframework.lang.Nullable; 049import org.springframework.transaction.support.ResourceHolderSupport; 050import org.springframework.transaction.support.TransactionSynchronizationManager; 051import org.springframework.util.Assert; 052import org.springframework.util.ReflectionUtils; 053 054/** 055 * Helper class that simplifies Hibernate data access code. Automatically 056 * converts HibernateExceptions into DataAccessExceptions, following the 057 * {@code org.springframework.dao} exception hierarchy. 058 * 059 * <p>The central method is {@code execute}, supporting Hibernate access code 060 * implementing the {@link HibernateCallback} interface. It provides Hibernate Session 061 * handling such that neither the HibernateCallback implementation nor the calling 062 * code needs to explicitly care about retrieving/closing Hibernate Sessions, 063 * or handling Session lifecycle exceptions. For typical single step actions, 064 * there are various convenience methods (find, load, saveOrUpdate, delete). 065 * 066 * <p>Can be used within a service implementation via direct instantiation 067 * with a SessionFactory reference, or get prepared in an application context 068 * and given to services as bean reference. Note: The SessionFactory should 069 * always be configured as bean in the application context, in the first case 070 * given to the service directly, in the second case to the prepared template. 071 * 072 * <p><b>NOTE: Hibernate access code can also be coded against the native Hibernate 073 * {@link Session}. Hence, for newly started projects, consider adopting the standard 074 * Hibernate style of coding against {@link SessionFactory#getCurrentSession()}. 075 * Alternatively, use {@link #execute(HibernateCallback)} with Java 8 lambda code blocks 076 * against the callback-provided {@code Session} which results in elegant code as well, 077 * decoupled from the Hibernate Session lifecycle. The remaining operations on this 078 * HibernateTemplate are deprecated in the meantime and primarily exist as a migration 079 * helper for older Hibernate 3.x/4.x data access code in existing applications.</b> 080 * 081 * @author Juergen Hoeller 082 * @since 4.2 083 * @see #setSessionFactory 084 * @see HibernateCallback 085 * @see Session 086 * @see LocalSessionFactoryBean 087 * @see HibernateTransactionManager 088 * @see org.springframework.orm.hibernate5.support.OpenSessionInViewFilter 089 * @see org.springframework.orm.hibernate5.support.OpenSessionInViewInterceptor 090 */ 091public class HibernateTemplate implements HibernateOperations, InitializingBean { 092 093 private static final Method createQueryMethod; 094 095 private static final Method getNamedQueryMethod; 096 097 static { 098 // Hibernate 5.2's createQuery method declares a new subtype as return type, 099 // so we need to use reflection for binary compatibility with 5.0/5.1 here. 100 try { 101 createQueryMethod = Session.class.getMethod("createQuery", String.class); 102 getNamedQueryMethod = Session.class.getMethod("getNamedQuery", String.class); 103 } 104 catch (NoSuchMethodException ex) { 105 throw new IllegalStateException("Incompatible Hibernate Session API", ex); 106 } 107 } 108 109 110 protected final Log logger = LogFactory.getLog(getClass()); 111 112 @Nullable 113 private SessionFactory sessionFactory; 114 115 @Nullable 116 private String[] filterNames; 117 118 private boolean exposeNativeSession = false; 119 120 private boolean checkWriteOperations = true; 121 122 private boolean cacheQueries = false; 123 124 @Nullable 125 private String queryCacheRegion; 126 127 private int fetchSize = 0; 128 129 private int maxResults = 0; 130 131 132 /** 133 * Create a new HibernateTemplate instance. 134 */ 135 public HibernateTemplate() { 136 } 137 138 /** 139 * Create a new HibernateTemplate instance. 140 * @param sessionFactory the SessionFactory to create Sessions with 141 */ 142 public HibernateTemplate(SessionFactory sessionFactory) { 143 setSessionFactory(sessionFactory); 144 afterPropertiesSet(); 145 } 146 147 148 /** 149 * Set the Hibernate SessionFactory that should be used to create 150 * Hibernate Sessions. 151 */ 152 public void setSessionFactory(@Nullable SessionFactory sessionFactory) { 153 this.sessionFactory = sessionFactory; 154 } 155 156 /** 157 * Return the Hibernate SessionFactory that should be used to create 158 * Hibernate Sessions. 159 */ 160 @Nullable 161 public SessionFactory getSessionFactory() { 162 return this.sessionFactory; 163 } 164 165 /** 166 * Obtain the SessionFactory for actual use. 167 * @return the SessionFactory (never {@code null}) 168 * @throws IllegalStateException in case of no SessionFactory set 169 * @since 5.0 170 */ 171 protected final SessionFactory obtainSessionFactory() { 172 SessionFactory sessionFactory = getSessionFactory(); 173 Assert.state(sessionFactory != null, "No SessionFactory set"); 174 return sessionFactory; 175 } 176 177 /** 178 * Set one or more names of Hibernate filters to be activated for all 179 * Sessions that this accessor works with. 180 * <p>Each of those filters will be enabled at the beginning of each 181 * operation and correspondingly disabled at the end of the operation. 182 * This will work for newly opened Sessions as well as for existing 183 * Sessions (for example, within a transaction). 184 * @see #enableFilters(Session) 185 * @see Session#enableFilter(String) 186 */ 187 public void setFilterNames(@Nullable String... filterNames) { 188 this.filterNames = filterNames; 189 } 190 191 /** 192 * Return the names of Hibernate filters to be activated, if any. 193 */ 194 @Nullable 195 public String[] getFilterNames() { 196 return this.filterNames; 197 } 198 199 /** 200 * Set whether to expose the native Hibernate Session to 201 * HibernateCallback code. 202 * <p>Default is "false": a Session proxy will be returned, suppressing 203 * {@code close} calls and automatically applying query cache 204 * settings and transaction timeouts. 205 * @see HibernateCallback 206 * @see Session 207 * @see #setCacheQueries 208 * @see #setQueryCacheRegion 209 * @see #prepareQuery 210 * @see #prepareCriteria 211 */ 212 public void setExposeNativeSession(boolean exposeNativeSession) { 213 this.exposeNativeSession = exposeNativeSession; 214 } 215 216 /** 217 * Return whether to expose the native Hibernate Session to 218 * HibernateCallback code, or rather a Session proxy. 219 */ 220 public boolean isExposeNativeSession() { 221 return this.exposeNativeSession; 222 } 223 224 /** 225 * Set whether to check that the Hibernate Session is not in read-only mode 226 * in case of write operations (save/update/delete). 227 * <p>Default is "true", for fail-fast behavior when attempting write operations 228 * within a read-only transaction. Turn this off to allow save/update/delete 229 * on a Session with flush mode MANUAL. 230 * @see #checkWriteOperationAllowed 231 * @see org.springframework.transaction.TransactionDefinition#isReadOnly 232 */ 233 public void setCheckWriteOperations(boolean checkWriteOperations) { 234 this.checkWriteOperations = checkWriteOperations; 235 } 236 237 /** 238 * Return whether to check that the Hibernate Session is not in read-only 239 * mode in case of write operations (save/update/delete). 240 */ 241 public boolean isCheckWriteOperations() { 242 return this.checkWriteOperations; 243 } 244 245 /** 246 * Set whether to cache all queries executed by this template. 247 * <p>If this is "true", all Query and Criteria objects created by 248 * this template will be marked as cacheable (including all 249 * queries through find methods). 250 * <p>To specify the query region to be used for queries cached 251 * by this template, set the "queryCacheRegion" property. 252 * @see #setQueryCacheRegion 253 * @see org.hibernate.Query#setCacheable 254 * @see Criteria#setCacheable 255 */ 256 public void setCacheQueries(boolean cacheQueries) { 257 this.cacheQueries = cacheQueries; 258 } 259 260 /** 261 * Return whether to cache all queries executed by this template. 262 */ 263 public boolean isCacheQueries() { 264 return this.cacheQueries; 265 } 266 267 /** 268 * Set the name of the cache region for queries executed by this template. 269 * <p>If this is specified, it will be applied to all Query and Criteria objects 270 * created by this template (including all queries through find methods). 271 * <p>The cache region will not take effect unless queries created by this 272 * template are configured to be cached via the "cacheQueries" property. 273 * @see #setCacheQueries 274 * @see org.hibernate.Query#setCacheRegion 275 * @see Criteria#setCacheRegion 276 */ 277 public void setQueryCacheRegion(@Nullable String queryCacheRegion) { 278 this.queryCacheRegion = queryCacheRegion; 279 } 280 281 /** 282 * Return the name of the cache region for queries executed by this template. 283 */ 284 @Nullable 285 public String getQueryCacheRegion() { 286 return this.queryCacheRegion; 287 } 288 289 /** 290 * Set the fetch size for this HibernateTemplate. This is important for processing 291 * large result sets: Setting this higher than the default value will increase 292 * processing speed at the cost of memory consumption; setting this lower can 293 * avoid transferring row data that will never be read by the application. 294 * <p>Default is 0, indicating to use the JDBC driver's default. 295 */ 296 public void setFetchSize(int fetchSize) { 297 this.fetchSize = fetchSize; 298 } 299 300 /** 301 * Return the fetch size specified for this HibernateTemplate. 302 */ 303 public int getFetchSize() { 304 return this.fetchSize; 305 } 306 307 /** 308 * Set the maximum number of rows for this HibernateTemplate. This is important 309 * for processing subsets of large result sets, avoiding to read and hold 310 * the entire result set in the database or in the JDBC driver if we're 311 * never interested in the entire result in the first place (for example, 312 * when performing searches that might return a large number of matches). 313 * <p>Default is 0, indicating to use the JDBC driver's default. 314 */ 315 public void setMaxResults(int maxResults) { 316 this.maxResults = maxResults; 317 } 318 319 /** 320 * Return the maximum number of rows specified for this HibernateTemplate. 321 */ 322 public int getMaxResults() { 323 return this.maxResults; 324 } 325 326 @Override 327 public void afterPropertiesSet() { 328 if (getSessionFactory() == null) { 329 throw new IllegalArgumentException("Property 'sessionFactory' is required"); 330 } 331 } 332 333 334 @Override 335 @Nullable 336 public <T> T execute(HibernateCallback<T> action) throws DataAccessException { 337 return doExecute(action, false); 338 } 339 340 /** 341 * Execute the action specified by the given action object within a 342 * native {@link Session}. 343 * <p>This execute variant overrides the template-wide 344 * {@link #isExposeNativeSession() "exposeNativeSession"} setting. 345 * @param action callback object that specifies the Hibernate action 346 * @return a result object returned by the action, or {@code null} 347 * @throws DataAccessException in case of Hibernate errors 348 */ 349 @Nullable 350 public <T> T executeWithNativeSession(HibernateCallback<T> action) { 351 return doExecute(action, true); 352 } 353 354 /** 355 * Execute the action specified by the given action object within a Session. 356 * @param action callback object that specifies the Hibernate action 357 * @param enforceNativeSession whether to enforce exposure of the native 358 * Hibernate Session to callback code 359 * @return a result object returned by the action, or {@code null} 360 * @throws DataAccessException in case of Hibernate errors 361 */ 362 @SuppressWarnings("deprecation") 363 @Nullable 364 protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNativeSession) throws DataAccessException { 365 Assert.notNull(action, "Callback object must not be null"); 366 367 Session session = null; 368 boolean isNew = false; 369 try { 370 session = obtainSessionFactory().getCurrentSession(); 371 } 372 catch (HibernateException ex) { 373 logger.debug("Could not retrieve pre-bound Hibernate session", ex); 374 } 375 if (session == null) { 376 session = obtainSessionFactory().openSession(); 377 session.setFlushMode(FlushMode.MANUAL); 378 isNew = true; 379 } 380 381 try { 382 enableFilters(session); 383 Session sessionToExpose = 384 (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session)); 385 return action.doInHibernate(sessionToExpose); 386 } 387 catch (HibernateException ex) { 388 throw SessionFactoryUtils.convertHibernateAccessException(ex); 389 } 390 catch (PersistenceException ex) { 391 if (ex.getCause() instanceof HibernateException) { 392 throw SessionFactoryUtils.convertHibernateAccessException((HibernateException) ex.getCause()); 393 } 394 throw ex; 395 } 396 catch (RuntimeException ex) { 397 // Callback code threw application exception... 398 throw ex; 399 } 400 finally { 401 if (isNew) { 402 SessionFactoryUtils.closeSession(session); 403 } 404 else { 405 disableFilters(session); 406 } 407 } 408 } 409 410 /** 411 * Create a close-suppressing proxy for the given Hibernate Session. 412 * The proxy also prepares returned Query and Criteria objects. 413 * @param session the Hibernate Session to create a proxy for 414 * @return the Session proxy 415 * @see Session#close() 416 * @see #prepareQuery 417 * @see #prepareCriteria 418 */ 419 protected Session createSessionProxy(Session session) { 420 return (Session) Proxy.newProxyInstance( 421 session.getClass().getClassLoader(), new Class<?>[] {Session.class}, 422 new CloseSuppressingInvocationHandler(session)); 423 } 424 425 /** 426 * Enable the specified filters on the given Session. 427 * @param session the current Hibernate Session 428 * @see #setFilterNames 429 * @see Session#enableFilter(String) 430 */ 431 protected void enableFilters(Session session) { 432 String[] filterNames = getFilterNames(); 433 if (filterNames != null) { 434 for (String filterName : filterNames) { 435 session.enableFilter(filterName); 436 } 437 } 438 } 439 440 /** 441 * Disable the specified filters on the given Session. 442 * @param session the current Hibernate Session 443 * @see #setFilterNames 444 * @see Session#disableFilter(String) 445 */ 446 protected void disableFilters(Session session) { 447 String[] filterNames = getFilterNames(); 448 if (filterNames != null) { 449 for (String filterName : filterNames) { 450 session.disableFilter(filterName); 451 } 452 } 453 } 454 455 456 //------------------------------------------------------------------------- 457 // Convenience methods for loading individual objects 458 //------------------------------------------------------------------------- 459 460 @Override 461 @Nullable 462 public <T> T get(Class<T> entityClass, Serializable id) throws DataAccessException { 463 return get(entityClass, id, null); 464 } 465 466 @Override 467 @Nullable 468 public <T> T get(final Class<T> entityClass, final Serializable id, @Nullable final LockMode lockMode) 469 throws DataAccessException { 470 471 return executeWithNativeSession(session -> { 472 if (lockMode != null) { 473 return session.get(entityClass, id, new LockOptions(lockMode)); 474 } 475 else { 476 return session.get(entityClass, id); 477 } 478 }); 479 } 480 481 @Override 482 @Nullable 483 public Object get(String entityName, Serializable id) throws DataAccessException { 484 return get(entityName, id, null); 485 } 486 487 @Override 488 @Nullable 489 public Object get(final String entityName, final Serializable id, @Nullable final LockMode lockMode) 490 throws DataAccessException { 491 492 return executeWithNativeSession(session -> { 493 if (lockMode != null) { 494 return session.get(entityName, id, new LockOptions(lockMode)); 495 } 496 else { 497 return session.get(entityName, id); 498 } 499 }); 500 } 501 502 @Override 503 public <T> T load(Class<T> entityClass, Serializable id) throws DataAccessException { 504 return load(entityClass, id, null); 505 } 506 507 @Override 508 public <T> T load(final Class<T> entityClass, final Serializable id, @Nullable final LockMode lockMode) 509 throws DataAccessException { 510 511 return nonNull(executeWithNativeSession(session -> { 512 if (lockMode != null) { 513 return session.load(entityClass, id, new LockOptions(lockMode)); 514 } 515 else { 516 return session.load(entityClass, id); 517 } 518 })); 519 } 520 521 @Override 522 public Object load(String entityName, Serializable id) throws DataAccessException { 523 return load(entityName, id, null); 524 } 525 526 @Override 527 public Object load(final String entityName, final Serializable id, @Nullable final LockMode lockMode) 528 throws DataAccessException { 529 530 return nonNull(executeWithNativeSession(session -> { 531 if (lockMode != null) { 532 return session.load(entityName, id, new LockOptions(lockMode)); 533 } 534 else { 535 return session.load(entityName, id); 536 } 537 })); 538 } 539 540 @Override 541 @SuppressWarnings({"unchecked", "deprecation"}) 542 public <T> List<T> loadAll(final Class<T> entityClass) throws DataAccessException { 543 return nonNull(executeWithNativeSession((HibernateCallback<List<T>>) session -> { 544 Criteria criteria = session.createCriteria(entityClass); 545 criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); 546 prepareCriteria(criteria); 547 return criteria.list(); 548 })); 549 } 550 551 @Override 552 @SuppressWarnings({"deprecation"}) 553 public void load(final Object entity, final Serializable id) throws DataAccessException { 554 executeWithNativeSession(session -> { 555 session.load(entity, id); 556 return null; 557 }); 558 } 559 560 @Override 561 public void refresh(final Object entity) throws DataAccessException { 562 refresh(entity, null); 563 } 564 565 @Override 566 public void refresh(final Object entity, @Nullable final LockMode lockMode) throws DataAccessException { 567 executeWithNativeSession(session -> { 568 if (lockMode != null) { 569 session.refresh(entity, new LockOptions(lockMode)); 570 } 571 else { 572 session.refresh(entity); 573 } 574 return null; 575 }); 576 } 577 578 @Override 579 public boolean contains(final Object entity) throws DataAccessException { 580 Boolean result = executeWithNativeSession(session -> session.contains(entity)); 581 Assert.state(result != null, "No contains result"); 582 return result; 583 } 584 585 @Override 586 public void evict(final Object entity) throws DataAccessException { 587 executeWithNativeSession(session -> { 588 session.evict(entity); 589 return null; 590 }); 591 } 592 593 @Override 594 public void initialize(Object proxy) throws DataAccessException { 595 try { 596 Hibernate.initialize(proxy); 597 } 598 catch (HibernateException ex) { 599 throw SessionFactoryUtils.convertHibernateAccessException(ex); 600 } 601 } 602 603 @Override 604 public Filter enableFilter(String filterName) throws IllegalStateException { 605 Session session = obtainSessionFactory().getCurrentSession(); 606 Filter filter = session.getEnabledFilter(filterName); 607 if (filter == null) { 608 filter = session.enableFilter(filterName); 609 } 610 return filter; 611 } 612 613 614 //------------------------------------------------------------------------- 615 // Convenience methods for storing individual objects 616 //------------------------------------------------------------------------- 617 618 @Override 619 public void lock(final Object entity, final LockMode lockMode) throws DataAccessException { 620 executeWithNativeSession(session -> { 621 session.buildLockRequest(new LockOptions(lockMode)).lock(entity); 622 return null; 623 }); 624 } 625 626 @Override 627 public void lock(final String entityName, final Object entity, final LockMode lockMode) 628 throws DataAccessException { 629 630 executeWithNativeSession(session -> { 631 session.buildLockRequest(new LockOptions(lockMode)).lock(entityName, entity); 632 return null; 633 }); 634 } 635 636 @Override 637 public Serializable save(final Object entity) throws DataAccessException { 638 return nonNull(executeWithNativeSession(session -> { 639 checkWriteOperationAllowed(session); 640 return session.save(entity); 641 })); 642 } 643 644 @Override 645 public Serializable save(final String entityName, final Object entity) throws DataAccessException { 646 return nonNull(executeWithNativeSession(session -> { 647 checkWriteOperationAllowed(session); 648 return session.save(entityName, entity); 649 })); 650 } 651 652 @Override 653 public void update(Object entity) throws DataAccessException { 654 update(entity, null); 655 } 656 657 @Override 658 public void update(final Object entity, @Nullable final LockMode lockMode) throws DataAccessException { 659 executeWithNativeSession(session -> { 660 checkWriteOperationAllowed(session); 661 session.update(entity); 662 if (lockMode != null) { 663 session.buildLockRequest(new LockOptions(lockMode)).lock(entity); 664 } 665 return null; 666 }); 667 } 668 669 @Override 670 public void update(String entityName, Object entity) throws DataAccessException { 671 update(entityName, entity, null); 672 } 673 674 @Override 675 public void update(final String entityName, final Object entity, @Nullable final LockMode lockMode) 676 throws DataAccessException { 677 678 executeWithNativeSession(session -> { 679 checkWriteOperationAllowed(session); 680 session.update(entityName, entity); 681 if (lockMode != null) { 682 session.buildLockRequest(new LockOptions(lockMode)).lock(entityName, entity); 683 } 684 return null; 685 }); 686 } 687 688 @Override 689 public void saveOrUpdate(final Object entity) throws DataAccessException { 690 executeWithNativeSession(session -> { 691 checkWriteOperationAllowed(session); 692 session.saveOrUpdate(entity); 693 return null; 694 }); 695 } 696 697 @Override 698 public void saveOrUpdate(final String entityName, final Object entity) throws DataAccessException { 699 executeWithNativeSession(session -> { 700 checkWriteOperationAllowed(session); 701 session.saveOrUpdate(entityName, entity); 702 return null; 703 }); 704 } 705 706 @Override 707 public void replicate(final Object entity, final ReplicationMode replicationMode) 708 throws DataAccessException { 709 710 executeWithNativeSession(session -> { 711 checkWriteOperationAllowed(session); 712 session.replicate(entity, replicationMode); 713 return null; 714 }); 715 } 716 717 @Override 718 public void replicate(final String entityName, final Object entity, final ReplicationMode replicationMode) 719 throws DataAccessException { 720 721 executeWithNativeSession(session -> { 722 checkWriteOperationAllowed(session); 723 session.replicate(entityName, entity, replicationMode); 724 return null; 725 }); 726 } 727 728 @Override 729 public void persist(final Object entity) throws DataAccessException { 730 executeWithNativeSession(session -> { 731 checkWriteOperationAllowed(session); 732 session.persist(entity); 733 return null; 734 }); 735 } 736 737 @Override 738 public void persist(final String entityName, final Object entity) throws DataAccessException { 739 executeWithNativeSession(session -> { 740 checkWriteOperationAllowed(session); 741 session.persist(entityName, entity); 742 return null; 743 }); 744 } 745 746 @Override 747 @SuppressWarnings("unchecked") 748 public <T> T merge(final T entity) throws DataAccessException { 749 return nonNull(executeWithNativeSession(session -> { 750 checkWriteOperationAllowed(session); 751 return (T) session.merge(entity); 752 })); 753 } 754 755 @Override 756 @SuppressWarnings("unchecked") 757 public <T> T merge(final String entityName, final T entity) throws DataAccessException { 758 return nonNull(executeWithNativeSession(session -> { 759 checkWriteOperationAllowed(session); 760 return (T) session.merge(entityName, entity); 761 })); 762 } 763 764 @Override 765 public void delete(Object entity) throws DataAccessException { 766 delete(entity, null); 767 } 768 769 @Override 770 public void delete(final Object entity, @Nullable final LockMode lockMode) throws DataAccessException { 771 executeWithNativeSession(session -> { 772 checkWriteOperationAllowed(session); 773 if (lockMode != null) { 774 session.buildLockRequest(new LockOptions(lockMode)).lock(entity); 775 } 776 session.delete(entity); 777 return null; 778 }); 779 } 780 781 @Override 782 public void delete(String entityName, Object entity) throws DataAccessException { 783 delete(entityName, entity, null); 784 } 785 786 @Override 787 public void delete(final String entityName, final Object entity, @Nullable final LockMode lockMode) 788 throws DataAccessException { 789 790 executeWithNativeSession(session -> { 791 checkWriteOperationAllowed(session); 792 if (lockMode != null) { 793 session.buildLockRequest(new LockOptions(lockMode)).lock(entityName, entity); 794 } 795 session.delete(entityName, entity); 796 return null; 797 }); 798 } 799 800 @Override 801 public void deleteAll(final Collection<?> entities) throws DataAccessException { 802 executeWithNativeSession(session -> { 803 checkWriteOperationAllowed(session); 804 for (Object entity : entities) { 805 session.delete(entity); 806 } 807 return null; 808 }); 809 } 810 811 @Override 812 public void flush() throws DataAccessException { 813 executeWithNativeSession(session -> { 814 session.flush(); 815 return null; 816 }); 817 } 818 819 @Override 820 public void clear() throws DataAccessException { 821 executeWithNativeSession(session -> { 822 session.clear(); 823 return null; 824 }); 825 } 826 827 828 //------------------------------------------------------------------------- 829 // Convenience finder methods for detached criteria 830 //------------------------------------------------------------------------- 831 832 @Override 833 public List<?> findByCriteria(DetachedCriteria criteria) throws DataAccessException { 834 return findByCriteria(criteria, -1, -1); 835 } 836 837 @Override 838 @SuppressWarnings("unchecked") 839 public List<?> findByCriteria(final DetachedCriteria criteria, final int firstResult, final int maxResults) 840 throws DataAccessException { 841 842 Assert.notNull(criteria, "DetachedCriteria must not be null"); 843 return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> { 844 Criteria executableCriteria = criteria.getExecutableCriteria(session); 845 prepareCriteria(executableCriteria); 846 if (firstResult >= 0) { 847 executableCriteria.setFirstResult(firstResult); 848 } 849 if (maxResults > 0) { 850 executableCriteria.setMaxResults(maxResults); 851 } 852 return executableCriteria.list(); 853 })); 854 } 855 856 @Override 857 public <T> List<T> findByExample(T exampleEntity) throws DataAccessException { 858 return findByExample(null, exampleEntity, -1, -1); 859 } 860 861 @Override 862 public <T> List<T> findByExample(String entityName, T exampleEntity) throws DataAccessException { 863 return findByExample(entityName, exampleEntity, -1, -1); 864 } 865 866 @Override 867 public <T> List<T> findByExample(T exampleEntity, int firstResult, int maxResults) throws DataAccessException { 868 return findByExample(null, exampleEntity, firstResult, maxResults); 869 } 870 871 @Override 872 @SuppressWarnings({"unchecked", "deprecation"}) 873 public <T> List<T> findByExample( 874 @Nullable final String entityName, final T exampleEntity, final int firstResult, final int maxResults) 875 throws DataAccessException { 876 877 Assert.notNull(exampleEntity, "Example entity must not be null"); 878 return nonNull(executeWithNativeSession((HibernateCallback<List<T>>) session -> { 879 Criteria executableCriteria = (entityName != null ? 880 session.createCriteria(entityName) : session.createCriteria(exampleEntity.getClass())); 881 executableCriteria.add(Example.create(exampleEntity)); 882 prepareCriteria(executableCriteria); 883 if (firstResult >= 0) { 884 executableCriteria.setFirstResult(firstResult); 885 } 886 if (maxResults > 0) { 887 executableCriteria.setMaxResults(maxResults); 888 } 889 return executableCriteria.list(); 890 })); 891 } 892 893 894 //------------------------------------------------------------------------- 895 // Convenience finder methods for HQL strings 896 //------------------------------------------------------------------------- 897 898 @Deprecated 899 @Override 900 @SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) 901 public List<?> find(final String queryString, @Nullable final Object... values) throws DataAccessException { 902 return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> { 903 org.hibernate.Query queryObject = queryObject( 904 ReflectionUtils.invokeMethod(createQueryMethod, session, queryString)); 905 prepareQuery(queryObject); 906 if (values != null) { 907 for (int i = 0; i < values.length; i++) { 908 queryObject.setParameter(i, values[i]); 909 } 910 } 911 return queryObject.list(); 912 })); 913 } 914 915 @Deprecated 916 @Override 917 public List<?> findByNamedParam(String queryString, String paramName, Object value) 918 throws DataAccessException { 919 920 return findByNamedParam(queryString, new String[] {paramName}, new Object[] {value}); 921 } 922 923 @Deprecated 924 @Override 925 @SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) 926 public List<?> findByNamedParam(final String queryString, final String[] paramNames, final Object[] values) 927 throws DataAccessException { 928 929 if (paramNames.length != values.length) { 930 throw new IllegalArgumentException("Length of paramNames array must match length of values array"); 931 } 932 return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> { 933 org.hibernate.Query queryObject = queryObject( 934 ReflectionUtils.invokeMethod(createQueryMethod, session, queryString)); 935 prepareQuery(queryObject); 936 for (int i = 0; i < values.length; i++) { 937 applyNamedParameterToQuery(queryObject, paramNames[i], values[i]); 938 } 939 return queryObject.list(); 940 })); 941 } 942 943 @Deprecated 944 @Override 945 @SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) 946 public List<?> findByValueBean(final String queryString, final Object valueBean) 947 throws DataAccessException { 948 949 return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> { 950 org.hibernate.Query queryObject = queryObject( 951 ReflectionUtils.invokeMethod(createQueryMethod, session, queryString)); 952 prepareQuery(queryObject); 953 queryObject.setProperties(valueBean); 954 return queryObject.list(); 955 })); 956 } 957 958 959 //------------------------------------------------------------------------- 960 // Convenience finder methods for named queries 961 //------------------------------------------------------------------------- 962 963 @Deprecated 964 @Override 965 @SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) 966 public List<?> findByNamedQuery(final String queryName, @Nullable final Object... values) throws DataAccessException { 967 return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> { 968 org.hibernate.Query queryObject = queryObject( 969 ReflectionUtils.invokeMethod(getNamedQueryMethod, session, queryName)); 970 prepareQuery(queryObject); 971 if (values != null) { 972 for (int i = 0; i < values.length; i++) { 973 queryObject.setParameter(i, values[i]); 974 } 975 } 976 return queryObject.list(); 977 })); 978 } 979 980 @Deprecated 981 @Override 982 public List<?> findByNamedQueryAndNamedParam(String queryName, String paramName, Object value) 983 throws DataAccessException { 984 985 return findByNamedQueryAndNamedParam(queryName, new String[] {paramName}, new Object[] {value}); 986 } 987 988 @Deprecated 989 @Override 990 @SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) 991 public List<?> findByNamedQueryAndNamedParam( 992 final String queryName, @Nullable final String[] paramNames, @Nullable final Object[] values) 993 throws DataAccessException { 994 995 if (values != null && (paramNames == null || paramNames.length != values.length)) { 996 throw new IllegalArgumentException("Length of paramNames array must match length of values array"); 997 } 998 return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> { 999 org.hibernate.Query queryObject = (org.hibernate.Query) 1000 nonNull(ReflectionUtils.invokeMethod(getNamedQueryMethod, session, queryName)); 1001 prepareQuery(queryObject); 1002 if (values != null) { 1003 for (int i = 0; i < values.length; i++) { 1004 applyNamedParameterToQuery(queryObject, paramNames[i], values[i]); 1005 } 1006 } 1007 return queryObject.list(); 1008 })); 1009 } 1010 1011 @Deprecated 1012 @Override 1013 @SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) 1014 public List<?> findByNamedQueryAndValueBean(final String queryName, final Object valueBean) 1015 throws DataAccessException { 1016 1017 return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> { 1018 org.hibernate.Query queryObject = queryObject( 1019 ReflectionUtils.invokeMethod(getNamedQueryMethod, session, queryName)); 1020 prepareQuery(queryObject); 1021 queryObject.setProperties(valueBean); 1022 return queryObject.list(); 1023 })); 1024 } 1025 1026 1027 //------------------------------------------------------------------------- 1028 // Convenience query methods for iteration and bulk updates/deletes 1029 //------------------------------------------------------------------------- 1030 1031 @Deprecated 1032 @Override 1033 @SuppressWarnings({"rawtypes", "deprecation"}) 1034 public Iterator<?> iterate(final String queryString, @Nullable final Object... values) throws DataAccessException { 1035 return nonNull(executeWithNativeSession((HibernateCallback<Iterator<?>>) session -> { 1036 org.hibernate.Query queryObject = queryObject( 1037 ReflectionUtils.invokeMethod(createQueryMethod, session, queryString)); 1038 prepareQuery(queryObject); 1039 if (values != null) { 1040 for (int i = 0; i < values.length; i++) { 1041 queryObject.setParameter(i, values[i]); 1042 } 1043 } 1044 return queryObject.iterate(); 1045 })); 1046 } 1047 1048 @Deprecated 1049 @Override 1050 public void closeIterator(Iterator<?> it) throws DataAccessException { 1051 try { 1052 Hibernate.close(it); 1053 } 1054 catch (HibernateException ex) { 1055 throw SessionFactoryUtils.convertHibernateAccessException(ex); 1056 } 1057 } 1058 1059 @Deprecated 1060 @Override 1061 @SuppressWarnings({"rawtypes", "deprecation"}) 1062 public int bulkUpdate(final String queryString, @Nullable final Object... values) throws DataAccessException { 1063 Integer result = executeWithNativeSession(session -> { 1064 org.hibernate.Query queryObject = queryObject( 1065 ReflectionUtils.invokeMethod(createQueryMethod, session, queryString)); 1066 prepareQuery(queryObject); 1067 if (values != null) { 1068 for (int i = 0; i < values.length; i++) { 1069 queryObject.setParameter(i, values[i]); 1070 } 1071 } 1072 return queryObject.executeUpdate(); 1073 }); 1074 Assert.state(result != null, "No update count"); 1075 return result; 1076 } 1077 1078 1079 //------------------------------------------------------------------------- 1080 // Helper methods used by the operations above 1081 //------------------------------------------------------------------------- 1082 1083 /** 1084 * Check whether write operations are allowed on the given Session. 1085 * <p>Default implementation throws an InvalidDataAccessApiUsageException in 1086 * case of {@code FlushMode.MANUAL}. Can be overridden in subclasses. 1087 * @param session current Hibernate Session 1088 * @throws InvalidDataAccessApiUsageException if write operations are not allowed 1089 * @see #setCheckWriteOperations 1090 * @see Session#getFlushMode() 1091 * @see FlushMode#MANUAL 1092 */ 1093 protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException { 1094 if (isCheckWriteOperations() && SessionFactoryUtils.getFlushMode(session).lessThan(FlushMode.COMMIT)) { 1095 throw new InvalidDataAccessApiUsageException( 1096 "Write operations are not allowed in read-only mode (FlushMode.MANUAL): "+ 1097 "Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition."); 1098 } 1099 } 1100 1101 /** 1102 * Prepare the given Criteria object, applying cache settings and/or 1103 * a transaction timeout. 1104 * @param criteria the Criteria object to prepare 1105 * @see #setCacheQueries 1106 * @see #setQueryCacheRegion 1107 */ 1108 protected void prepareCriteria(Criteria criteria) { 1109 if (isCacheQueries()) { 1110 criteria.setCacheable(true); 1111 if (getQueryCacheRegion() != null) { 1112 criteria.setCacheRegion(getQueryCacheRegion()); 1113 } 1114 } 1115 if (getFetchSize() > 0) { 1116 criteria.setFetchSize(getFetchSize()); 1117 } 1118 if (getMaxResults() > 0) { 1119 criteria.setMaxResults(getMaxResults()); 1120 } 1121 1122 ResourceHolderSupport sessionHolder = 1123 (ResourceHolderSupport) TransactionSynchronizationManager.getResource(obtainSessionFactory()); 1124 if (sessionHolder != null && sessionHolder.hasTimeout()) { 1125 criteria.setTimeout(sessionHolder.getTimeToLiveInSeconds()); 1126 } 1127 } 1128 1129 /** 1130 * Prepare the given Query object, applying cache settings and/or 1131 * a transaction timeout. 1132 * @param queryObject the Query object to prepare 1133 * @see #setCacheQueries 1134 * @see #setQueryCacheRegion 1135 */ 1136 @SuppressWarnings({"rawtypes", "deprecation"}) 1137 protected void prepareQuery(org.hibernate.Query queryObject) { 1138 if (isCacheQueries()) { 1139 queryObject.setCacheable(true); 1140 if (getQueryCacheRegion() != null) { 1141 queryObject.setCacheRegion(getQueryCacheRegion()); 1142 } 1143 } 1144 if (getFetchSize() > 0) { 1145 queryObject.setFetchSize(getFetchSize()); 1146 } 1147 if (getMaxResults() > 0) { 1148 queryObject.setMaxResults(getMaxResults()); 1149 } 1150 1151 ResourceHolderSupport sessionHolder = 1152 (ResourceHolderSupport) TransactionSynchronizationManager.getResource(obtainSessionFactory()); 1153 if (sessionHolder != null && sessionHolder.hasTimeout()) { 1154 queryObject.setTimeout(sessionHolder.getTimeToLiveInSeconds()); 1155 } 1156 } 1157 1158 /** 1159 * Apply the given name parameter to the given Query object. 1160 * @param queryObject the Query object 1161 * @param paramName the name of the parameter 1162 * @param value the value of the parameter 1163 * @throws HibernateException if thrown by the Query object 1164 */ 1165 @Deprecated 1166 @SuppressWarnings({"rawtypes", "deprecation"}) 1167 protected void applyNamedParameterToQuery(org.hibernate.Query queryObject, String paramName, Object value) 1168 throws HibernateException { 1169 1170 if (value instanceof Collection) { 1171 queryObject.setParameterList(paramName, (Collection<?>) value); 1172 } 1173 else if (value instanceof Object[]) { 1174 queryObject.setParameterList(paramName, (Object[]) value); 1175 } 1176 else { 1177 queryObject.setParameter(paramName, value); 1178 } 1179 } 1180 1181 @Deprecated 1182 @SuppressWarnings({"rawtypes", "deprecation"}) 1183 private static org.hibernate.Query queryObject(@Nullable Object result) { 1184 Assert.state(result != null, "No Hibernate Query"); 1185 return (org.hibernate.Query) result; 1186 } 1187 1188 private static <T> T nonNull(@Nullable T result) { 1189 Assert.state(result != null, "No result"); 1190 return result; 1191 } 1192 1193 1194 /** 1195 * Invocation handler that suppresses close calls on Hibernate Sessions. 1196 * Also prepares returned Query and Criteria objects. 1197 * @see Session#close 1198 */ 1199 private class CloseSuppressingInvocationHandler implements InvocationHandler { 1200 1201 private final Session target; 1202 1203 public CloseSuppressingInvocationHandler(Session target) { 1204 this.target = target; 1205 } 1206 1207 @Override 1208 @SuppressWarnings({"rawtypes", "deprecation"}) 1209 @Nullable 1210 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 1211 // Invocation on Session interface coming in... 1212 1213 if (method.getName().equals("equals")) { 1214 // Only consider equal when proxies are identical. 1215 return (proxy == args[0]); 1216 } 1217 else if (method.getName().equals("hashCode")) { 1218 // Use hashCode of Session proxy. 1219 return System.identityHashCode(proxy); 1220 } 1221 else if (method.getName().equals("close")) { 1222 // Handle close method: suppress, not valid. 1223 return null; 1224 } 1225 1226 // Invoke method on target Session. 1227 try { 1228 Object retVal = method.invoke(this.target, args); 1229 1230 // If return value is a Query or Criteria, apply transaction timeout. 1231 // Applies to createQuery, getNamedQuery, createCriteria. 1232 if (retVal instanceof Criteria) { 1233 prepareCriteria(((Criteria) retVal)); 1234 } 1235 else if (retVal instanceof org.hibernate.Query) { 1236 prepareQuery(((org.hibernate.Query) retVal)); 1237 } 1238 1239 return retVal; 1240 } 1241 catch (InvocationTargetException ex) { 1242 throw ex.getTargetException(); 1243 } 1244 } 1245 } 1246 1247}