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