001/* 002 * Copyright 2002-2014 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.hibernate3; 018 019import java.sql.SQLException; 020 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023import org.hibernate.FlushMode; 024import org.hibernate.HibernateException; 025import org.hibernate.Interceptor; 026import org.hibernate.JDBCException; 027import org.hibernate.Session; 028import org.hibernate.SessionFactory; 029import org.hibernate.exception.GenericJDBCException; 030 031import org.springframework.beans.BeansException; 032import org.springframework.beans.factory.BeanFactory; 033import org.springframework.beans.factory.BeanFactoryAware; 034import org.springframework.beans.factory.InitializingBean; 035import org.springframework.core.Constants; 036import org.springframework.dao.DataAccessException; 037import org.springframework.jdbc.support.SQLExceptionTranslator; 038 039/** 040 * Base class for {@link HibernateTemplate} and {@link HibernateInterceptor}, 041 * defining common properties such as SessionFactory and flushing behavior. 042 * 043 * <p>Not intended to be used directly. 044 * See {@link HibernateTemplate} and {@link HibernateInterceptor}. 045 * 046 * @author Juergen Hoeller 047 * @since 1.2 048 * @see HibernateTemplate 049 * @see HibernateInterceptor 050 * @see #setFlushMode 051 * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x 052 */ 053@Deprecated 054public abstract class HibernateAccessor implements InitializingBean, BeanFactoryAware { 055 056 /** 057 * Never flush is a good strategy for read-only units of work. 058 * Hibernate will not track and look for changes in this case, 059 * avoiding any overhead of modification detection. 060 * <p>In case of an existing Session, FLUSH_NEVER will turn the flush mode 061 * to NEVER for the scope of the current operation, resetting the previous 062 * flush mode afterwards. 063 * @see #setFlushMode 064 */ 065 public static final int FLUSH_NEVER = 0; 066 067 /** 068 * Automatic flushing is the default mode for a Hibernate Session. 069 * A session will get flushed on transaction commit, and on certain find 070 * operations that might involve already modified instances, but not 071 * after each unit of work like with eager flushing. 072 * <p>In case of an existing Session, FLUSH_AUTO will participate in the 073 * existing flush mode, not modifying it for the current operation. 074 * This in particular means that this setting will not modify an existing 075 * flush mode NEVER, in contrast to FLUSH_EAGER. 076 * @see #setFlushMode 077 */ 078 public static final int FLUSH_AUTO = 1; 079 080 /** 081 * Eager flushing leads to immediate synchronization with the database, 082 * even if in a transaction. This causes inconsistencies to show up and throw 083 * a respective exception immediately, and JDBC access code that participates 084 * in the same transaction will see the changes as the database is already 085 * aware of them then. But the drawbacks are: 086 * <ul> 087 * <li>additional communication roundtrips with the database, instead of a 088 * single batch at transaction commit; 089 * <li>the fact that an actual database rollback is needed if the Hibernate 090 * transaction rolls back (due to already submitted SQL statements). 091 * </ul> 092 * <p>In case of an existing Session, FLUSH_EAGER will turn the flush mode 093 * to AUTO for the scope of the current operation and issue a flush at the 094 * end, resetting the previous flush mode afterwards. 095 * @see #setFlushMode 096 */ 097 public static final int FLUSH_EAGER = 2; 098 099 /** 100 * Flushing at commit only is intended for units of work where no 101 * intermediate flushing is desired, not even for find operations 102 * that might involve already modified instances. 103 * <p>In case of an existing Session, FLUSH_COMMIT will turn the flush mode 104 * to COMMIT for the scope of the current operation, resetting the previous 105 * flush mode afterwards. The only exception is an existing flush mode 106 * NEVER, which will not be modified through this setting. 107 * @see #setFlushMode 108 */ 109 public static final int FLUSH_COMMIT = 3; 110 111 /** 112 * Flushing before every query statement is rarely necessary. 113 * It is only available for special needs. 114 * <p>In case of an existing Session, FLUSH_ALWAYS will turn the flush mode 115 * to ALWAYS for the scope of the current operation, resetting the previous 116 * flush mode afterwards. 117 * @see #setFlushMode 118 */ 119 public static final int FLUSH_ALWAYS = 4; 120 121 122 /** Constants instance for HibernateAccessor */ 123 private static final Constants constants = new Constants(HibernateAccessor.class); 124 125 /** Logger available to subclasses */ 126 protected final Log logger = LogFactory.getLog(getClass()); 127 128 private SessionFactory sessionFactory; 129 130 private Object entityInterceptor; 131 132 private SQLExceptionTranslator jdbcExceptionTranslator; 133 134 private SQLExceptionTranslator defaultJdbcExceptionTranslator; 135 136 private int flushMode = FLUSH_AUTO; 137 138 private String[] filterNames; 139 140 /** 141 * Just needed for entityInterceptorBeanName. 142 * @see #setEntityInterceptorBeanName 143 */ 144 private BeanFactory beanFactory; 145 146 147 /** 148 * Set the Hibernate SessionFactory that should be used to create 149 * Hibernate Sessions. 150 */ 151 public void setSessionFactory(SessionFactory sessionFactory) { 152 this.sessionFactory = sessionFactory; 153 } 154 155 /** 156 * Return the Hibernate SessionFactory that should be used to create 157 * Hibernate Sessions. 158 */ 159 public SessionFactory getSessionFactory() { 160 return this.sessionFactory; 161 } 162 163 /** 164 * Set the bean name of a Hibernate entity interceptor that allows to inspect 165 * and change property values before writing to and reading from the database. 166 * Will get applied to any new Session created by this transaction manager. 167 * <p>Requires the bean factory to be known, to be able to resolve the bean 168 * name to an interceptor instance on session creation. Typically used for 169 * prototype interceptors, i.e. a new interceptor instance per session. 170 * <p>Can also be used for shared interceptor instances, but it is recommended 171 * to set the interceptor reference directly in such a scenario. 172 * @param entityInterceptorBeanName the name of the entity interceptor in 173 * the bean factory 174 * @see #setBeanFactory 175 * @see #setEntityInterceptor 176 */ 177 public void setEntityInterceptorBeanName(String entityInterceptorBeanName) { 178 this.entityInterceptor = entityInterceptorBeanName; 179 } 180 181 /** 182 * Set a Hibernate entity interceptor that allows to inspect and change 183 * property values before writing to and reading from the database. 184 * Will get applied to any <b>new</b> Session created by this object. 185 * <p>Such an interceptor can either be set at the SessionFactory level, 186 * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on 187 * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager. 188 * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager 189 * to avoid repeated configuration and guarantee consistent behavior in transactions. 190 * @see #setEntityInterceptorBeanName 191 * @see LocalSessionFactoryBean#setEntityInterceptor 192 * @see HibernateTransactionManager#setEntityInterceptor 193 */ 194 public void setEntityInterceptor(Interceptor entityInterceptor) { 195 this.entityInterceptor = entityInterceptor; 196 } 197 198 /** 199 * Return the current Hibernate entity interceptor, or {@code null} if none. 200 * Resolves an entity interceptor bean name via the bean factory, 201 * if necessary. 202 * @throws IllegalStateException if bean name specified but no bean factory set 203 * @throws org.springframework.beans.BeansException if bean name resolution via the bean factory failed 204 * @see #setEntityInterceptor 205 * @see #setEntityInterceptorBeanName 206 * @see #setBeanFactory 207 */ 208 public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException { 209 if (this.entityInterceptor instanceof String) { 210 if (this.beanFactory == null) { 211 throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set"); 212 } 213 return this.beanFactory.getBean((String) this.entityInterceptor, Interceptor.class); 214 } 215 return (Interceptor) this.entityInterceptor; 216 } 217 218 /** 219 * Set the JDBC exception translator for this instance. 220 * <p>Applied to any SQLException root cause of a Hibernate JDBCException, 221 * overriding Hibernate's default SQLException translation (which is 222 * based on Hibernate's Dialect for a specific target database). 223 * @param jdbcExceptionTranslator the exception translator 224 * @see java.sql.SQLException 225 * @see org.hibernate.JDBCException 226 * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator 227 * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator 228 */ 229 public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) { 230 this.jdbcExceptionTranslator = jdbcExceptionTranslator; 231 } 232 233 /** 234 * Return the JDBC exception translator for this instance, if any. 235 */ 236 public SQLExceptionTranslator getJdbcExceptionTranslator() { 237 return this.jdbcExceptionTranslator; 238 } 239 240 /** 241 * Set the flush behavior by the name of the respective constant 242 * in this class, e.g. "FLUSH_AUTO". Default is "FLUSH_AUTO". 243 * @param constantName name of the constant 244 * @see #setFlushMode 245 * @see #FLUSH_AUTO 246 */ 247 public void setFlushModeName(String constantName) { 248 setFlushMode(constants.asNumber(constantName).intValue()); 249 } 250 251 /** 252 * Set the flush behavior to one of the constants in this class. 253 * Default is FLUSH_AUTO. 254 * @see #setFlushModeName 255 * @see #FLUSH_AUTO 256 */ 257 public void setFlushMode(int flushMode) { 258 this.flushMode = flushMode; 259 } 260 261 /** 262 * Return if a flush should be forced after executing the callback code. 263 */ 264 public int getFlushMode() { 265 return this.flushMode; 266 } 267 268 /** 269 * Set the name of a Hibernate filter to be activated for all 270 * Sessions that this accessor works with. 271 * <p>This filter will be enabled at the beginning of each operation 272 * and correspondingly disabled at the end of the operation. 273 * This will work for newly opened Sessions as well as for existing 274 * Sessions (for example, within a transaction). 275 * @see #enableFilters(org.hibernate.Session) 276 * @see org.hibernate.Session#enableFilter(String) 277 * @see LocalSessionFactoryBean#setFilterDefinitions 278 */ 279 public void setFilterName(String filter) { 280 this.filterNames = new String[] {filter}; 281 } 282 283 /** 284 * Set one or more names of Hibernate filters to be activated for all 285 * Sessions that this accessor works with. 286 * <p>Each of those filters will be enabled at the beginning of each 287 * operation and correspondingly disabled at the end of the operation. 288 * This will work for newly opened Sessions as well as for existing 289 * Sessions (for example, within a transaction). 290 * @see #enableFilters(org.hibernate.Session) 291 * @see org.hibernate.Session#enableFilter(String) 292 * @see LocalSessionFactoryBean#setFilterDefinitions 293 */ 294 public void setFilterNames(String... filterNames) { 295 this.filterNames = filterNames; 296 } 297 298 /** 299 * Return the names of Hibernate filters to be activated, if any. 300 */ 301 public String[] getFilterNames() { 302 return this.filterNames; 303 } 304 305 /** 306 * The bean factory just needs to be known for resolving entity interceptor 307 * bean names. It does not need to be set for any other mode of operation. 308 * @see #setEntityInterceptorBeanName 309 */ 310 @Override 311 public void setBeanFactory(BeanFactory beanFactory) { 312 this.beanFactory = beanFactory; 313 } 314 315 @Override 316 public void afterPropertiesSet() { 317 if (getSessionFactory() == null) { 318 throw new IllegalArgumentException("Property 'sessionFactory' is required"); 319 } 320 } 321 322 323 /** 324 * Apply the flush mode that's been specified for this accessor 325 * to the given Session. 326 * @param session the current Hibernate Session 327 * @param existingTransaction if executing within an existing transaction 328 * @return the previous flush mode to restore after the operation, 329 * or {@code null} if none 330 * @see #setFlushMode 331 * @see org.hibernate.Session#setFlushMode 332 */ 333 protected FlushMode applyFlushMode(Session session, boolean existingTransaction) { 334 if (getFlushMode() == FLUSH_NEVER) { 335 if (existingTransaction) { 336 FlushMode previousFlushMode = session.getFlushMode(); 337 if (!previousFlushMode.lessThan(FlushMode.COMMIT)) { 338 session.setFlushMode(FlushMode.MANUAL); 339 return previousFlushMode; 340 } 341 } 342 else { 343 session.setFlushMode(FlushMode.MANUAL); 344 } 345 } 346 else if (getFlushMode() == FLUSH_EAGER) { 347 if (existingTransaction) { 348 FlushMode previousFlushMode = session.getFlushMode(); 349 if (!previousFlushMode.equals(FlushMode.AUTO)) { 350 session.setFlushMode(FlushMode.AUTO); 351 return previousFlushMode; 352 } 353 } 354 else { 355 // rely on default FlushMode.AUTO 356 } 357 } 358 else if (getFlushMode() == FLUSH_COMMIT) { 359 if (existingTransaction) { 360 FlushMode previousFlushMode = session.getFlushMode(); 361 if (previousFlushMode.equals(FlushMode.AUTO) || previousFlushMode.equals(FlushMode.ALWAYS)) { 362 session.setFlushMode(FlushMode.COMMIT); 363 return previousFlushMode; 364 } 365 } 366 else { 367 session.setFlushMode(FlushMode.COMMIT); 368 } 369 } 370 else if (getFlushMode() == FLUSH_ALWAYS) { 371 if (existingTransaction) { 372 FlushMode previousFlushMode = session.getFlushMode(); 373 if (!previousFlushMode.equals(FlushMode.ALWAYS)) { 374 session.setFlushMode(FlushMode.ALWAYS); 375 return previousFlushMode; 376 } 377 } 378 else { 379 session.setFlushMode(FlushMode.ALWAYS); 380 } 381 } 382 return null; 383 } 384 385 /** 386 * Flush the given Hibernate Session if necessary. 387 * @param session the current Hibernate Session 388 * @param existingTransaction if executing within an existing transaction 389 * @throws HibernateException in case of Hibernate flushing errors 390 */ 391 protected void flushIfNecessary(Session session, boolean existingTransaction) throws HibernateException { 392 if (getFlushMode() == FLUSH_EAGER || (!existingTransaction && getFlushMode() != FLUSH_NEVER)) { 393 logger.debug("Eagerly flushing Hibernate session"); 394 session.flush(); 395 } 396 } 397 398 399 /** 400 * Convert the given HibernateException to an appropriate exception 401 * from the {@code org.springframework.dao} hierarchy. 402 * <p>Will automatically apply a specified SQLExceptionTranslator to a 403 * Hibernate JDBCException, else rely on Hibernate's default translation. 404 * @param ex HibernateException that occured 405 * @return a corresponding DataAccessException 406 * @see SessionFactoryUtils#convertHibernateAccessException 407 * @see #setJdbcExceptionTranslator 408 */ 409 public DataAccessException convertHibernateAccessException(HibernateException ex) { 410 if (getJdbcExceptionTranslator() != null && ex instanceof JDBCException) { 411 return convertJdbcAccessException((JDBCException) ex, getJdbcExceptionTranslator()); 412 } 413 else if (GenericJDBCException.class == ex.getClass()) { 414 return convertJdbcAccessException((GenericJDBCException) ex, getDefaultJdbcExceptionTranslator()); 415 } 416 return SessionFactoryUtils.convertHibernateAccessException(ex); 417 } 418 419 /** 420 * Convert the given Hibernate JDBCException to an appropriate exception 421 * from the {@code org.springframework.dao} hierarchy, using the 422 * given SQLExceptionTranslator. 423 * @param ex Hibernate JDBCException that occured 424 * @param translator the SQLExceptionTranslator to use 425 * @return a corresponding DataAccessException 426 */ 427 protected DataAccessException convertJdbcAccessException(JDBCException ex, SQLExceptionTranslator translator) { 428 return translator.translate("Hibernate operation: " + ex.getMessage(), ex.getSQL(), ex.getSQLException()); 429 } 430 431 /** 432 * Convert the given SQLException to an appropriate exception from the 433 * {@code org.springframework.dao} hierarchy. Can be overridden in subclasses. 434 * <p>Note that a direct SQLException can just occur when callback code 435 * performs direct JDBC access via {@code Session.connection()}. 436 * @param ex the SQLException 437 * @return the corresponding DataAccessException instance 438 * @see #setJdbcExceptionTranslator 439 */ 440 protected DataAccessException convertJdbcAccessException(SQLException ex) { 441 SQLExceptionTranslator translator = getJdbcExceptionTranslator(); 442 if (translator == null) { 443 translator = getDefaultJdbcExceptionTranslator(); 444 } 445 return translator.translate("Hibernate-related JDBC operation", null, ex); 446 } 447 448 /** 449 * Obtain a default SQLExceptionTranslator, lazily creating it if necessary. 450 * <p>Creates a default 451 * {@link org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator} 452 * for the SessionFactory's underlying DataSource. 453 */ 454 protected synchronized SQLExceptionTranslator getDefaultJdbcExceptionTranslator() { 455 if (this.defaultJdbcExceptionTranslator == null) { 456 this.defaultJdbcExceptionTranslator = SessionFactoryUtils.newJdbcExceptionTranslator(getSessionFactory()); 457 } 458 return this.defaultJdbcExceptionTranslator; 459 } 460 461 462 /** 463 * Enable the specified filters on the given Session. 464 * @param session the current Hibernate Session 465 * @see #setFilterNames 466 * @see org.hibernate.Session#enableFilter(String) 467 */ 468 protected void enableFilters(Session session) { 469 String[] filterNames = getFilterNames(); 470 if (filterNames != null) { 471 for (String filterName : filterNames) { 472 session.enableFilter(filterName); 473 } 474 } 475 } 476 477 /** 478 * Disable the specified filters on the given Session. 479 * @param session the current Hibernate Session 480 * @see #setFilterNames 481 * @see org.hibernate.Session#disableFilter(String) 482 */ 483 protected void disableFilters(Session session) { 484 String[] filterNames = getFilterNames(); 485 if (filterNames != null) { 486 for (String filterName : filterNames) { 487 session.disableFilter(filterName); 488 } 489 } 490 } 491 492}