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