1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.cfg;
26
27 import java.io.Serializable;
28 import java.lang.reflect.Method;
29 import java.sql.Connection;
30 import java.sql.DatabaseMetaData;
31 import java.sql.ResultSet;
32 import java.sql.SQLException;
33 import java.util.Map;
34 import java.util.Properties;
35
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import org.hibernate.ConnectionReleaseMode;
40 import org.hibernate.EntityMode;
41 import org.hibernate.HibernateException;
42 import org.hibernate.bytecode.BytecodeProvider;
43 import org.hibernate.cache.QueryCacheFactory;
44 import org.hibernate.cache.RegionFactory;
45 import org.hibernate.cache.impl.NoCachingRegionFactory;
46 import org.hibernate.cache.impl.bridge.RegionFactoryCacheProviderBridge;
47 import org.hibernate.connection.ConnectionProvider;
48 import org.hibernate.connection.ConnectionProviderFactory;
49 import org.hibernate.dialect.Dialect;
50 import org.hibernate.dialect.DialectFactory;
51 import org.hibernate.exception.SQLExceptionConverter;
52 import org.hibernate.exception.SQLExceptionConverterFactory;
53 import org.hibernate.hql.QueryTranslatorFactory;
54 import org.hibernate.jdbc.BatcherFactory;
55 import org.hibernate.jdbc.BatchingBatcherFactory;
56 import org.hibernate.jdbc.NonBatchingBatcherFactory;
57 import org.hibernate.jdbc.util.SQLStatementLogger;
58 import org.hibernate.transaction.TransactionFactory;
59 import org.hibernate.transaction.TransactionFactoryFactory;
60 import org.hibernate.transaction.TransactionManagerLookup;
61 import org.hibernate.transaction.TransactionManagerLookupFactory;
62 import org.hibernate.util.PropertiesHelper;
63 import org.hibernate.util.ReflectHelper;
64 import org.hibernate.util.StringHelper;
65
66 /**
67 * Reads configuration properties and configures a <tt>Settings</tt> instance.
68 *
69 * @author Gavin King
70 */
71 public class SettingsFactory implements Serializable {
72
73 public static final String DEF_CACHE_REG_FACTORY = NoCachingRegionFactory.class.getName();
74 private static final Logger log = LoggerFactory.getLogger(SettingsFactory.class);
75
76 protected SettingsFactory() {
77 }
78
79 public Settings buildSettings(Properties props) {
80 Settings settings = new Settings();
81
82 //SessionFactory name:
83
84 String sessionFactoryName = props.getProperty(Environment.SESSION_FACTORY_NAME);
85 settings.setSessionFactoryName(sessionFactoryName);
86
87 //JDBC and connection settings:
88
89 ConnectionProvider connections = createConnectionProvider(props);
90 settings.setConnectionProvider(connections);
91
92 //Interrogate JDBC metadata
93
94 String databaseName = null;
95 int databaseMajorVersion = 0;
96 boolean metaSupportsScrollable = false;
97 boolean metaSupportsGetGeneratedKeys = false;
98 boolean metaSupportsBatchUpdates = false;
99 boolean metaReportsDDLCausesTxnCommit = false;
100 boolean metaReportsDDLInTxnSupported = true;
101
102 // 'hibernate.temp.use_jdbc_metadata_defaults' is a temporary magic value.
103 // The need for it is intended to be alleviated with 3.3 developement, thus it is
104 // not defined as an Environment constant...
105 // it is used to control whether we should consult the JDBC metadata to determine
106 // certain Settings default values; it is useful to *not* do this when the database
107 // may not be available (mainly in tools usage).
108 boolean useJdbcMetadata = PropertiesHelper.getBoolean( "hibernate.temp.use_jdbc_metadata_defaults", props, true );
109 if ( useJdbcMetadata ) {
110 try {
111 Connection conn = connections.getConnection();
112 try {
113 DatabaseMetaData meta = conn.getMetaData();
114 databaseName = meta.getDatabaseProductName();
115 databaseMajorVersion = getDatabaseMajorVersion(meta);
116 log.info("RDBMS: " + databaseName + ", version: " + meta.getDatabaseProductVersion() );
117 log.info("JDBC driver: " + meta.getDriverName() + ", version: " + meta.getDriverVersion() );
118
119 metaSupportsScrollable = meta.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
120 metaSupportsBatchUpdates = meta.supportsBatchUpdates();
121 metaReportsDDLCausesTxnCommit = meta.dataDefinitionCausesTransactionCommit();
122 metaReportsDDLInTxnSupported = !meta.dataDefinitionIgnoredInTransactions();
123
124 if ( Environment.jvmSupportsGetGeneratedKeys() ) {
125 try {
126 Boolean result = (Boolean) DatabaseMetaData.class.getMethod("supportsGetGeneratedKeys", null)
127 .invoke(meta, null);
128 metaSupportsGetGeneratedKeys = result.booleanValue();
129 }
130 catch (AbstractMethodError ame) {
131 metaSupportsGetGeneratedKeys = false;
132 }
133 catch (Exception e) {
134 metaSupportsGetGeneratedKeys = false;
135 }
136 }
137
138 }
139 finally {
140 connections.closeConnection(conn);
141 }
142 }
143 catch (SQLException sqle) {
144 log.warn("Could not obtain connection metadata", sqle);
145 }
146 catch (UnsupportedOperationException uoe) {
147 // user supplied JDBC connections
148 }
149 }
150 settings.setDataDefinitionImplicitCommit( metaReportsDDLCausesTxnCommit );
151 settings.setDataDefinitionInTransactionSupported( metaReportsDDLInTxnSupported );
152
153
154 //SQL Dialect:
155 Dialect dialect = determineDialect( props, databaseName, databaseMajorVersion );
156 settings.setDialect(dialect);
157
158 //use dialect default properties
159 final Properties properties = new Properties();
160 properties.putAll( dialect.getDefaultProperties() );
161 properties.putAll(props);
162
163 // Transaction settings:
164
165 TransactionFactory transactionFactory = createTransactionFactory(properties);
166 settings.setTransactionFactory(transactionFactory);
167 settings.setTransactionManagerLookup( createTransactionManagerLookup(properties) );
168
169 boolean flushBeforeCompletion = PropertiesHelper.getBoolean(Environment.FLUSH_BEFORE_COMPLETION, properties);
170 log.info("Automatic flush during beforeCompletion(): " + enabledDisabled(flushBeforeCompletion) );
171 settings.setFlushBeforeCompletionEnabled(flushBeforeCompletion);
172
173 boolean autoCloseSession = PropertiesHelper.getBoolean(Environment.AUTO_CLOSE_SESSION, properties);
174 log.info("Automatic session close at end of transaction: " + enabledDisabled(autoCloseSession) );
175 settings.setAutoCloseSessionEnabled(autoCloseSession);
176
177 //JDBC and connection settings:
178
179 int batchSize = PropertiesHelper.getInt(Environment.STATEMENT_BATCH_SIZE, properties, 0);
180 if ( !metaSupportsBatchUpdates ) batchSize = 0;
181 if (batchSize>0) log.info("JDBC batch size: " + batchSize);
182 settings.setJdbcBatchSize(batchSize);
183 boolean jdbcBatchVersionedData = PropertiesHelper.getBoolean(Environment.BATCH_VERSIONED_DATA, properties, false);
184 if (batchSize>0) log.info("JDBC batch updates for versioned data: " + enabledDisabled(jdbcBatchVersionedData) );
185 settings.setJdbcBatchVersionedData(jdbcBatchVersionedData);
186 settings.setBatcherFactory( createBatcherFactory(properties, batchSize) );
187
188 boolean useScrollableResultSets = PropertiesHelper.getBoolean(Environment.USE_SCROLLABLE_RESULTSET, properties, metaSupportsScrollable);
189 log.info("Scrollable result sets: " + enabledDisabled(useScrollableResultSets) );
190 settings.setScrollableResultSetsEnabled(useScrollableResultSets);
191
192 boolean wrapResultSets = PropertiesHelper.getBoolean(Environment.WRAP_RESULT_SETS, properties, false);
193 log.debug( "Wrap result sets: " + enabledDisabled(wrapResultSets) );
194 settings.setWrapResultSetsEnabled(wrapResultSets);
195
196 boolean useGetGeneratedKeys = PropertiesHelper.getBoolean(Environment.USE_GET_GENERATED_KEYS, properties, metaSupportsGetGeneratedKeys);
197 log.info("JDBC3 getGeneratedKeys(): " + enabledDisabled(useGetGeneratedKeys) );
198 settings.setGetGeneratedKeysEnabled(useGetGeneratedKeys);
199
200 Integer statementFetchSize = PropertiesHelper.getInteger(Environment.STATEMENT_FETCH_SIZE, properties);
201 if (statementFetchSize!=null) log.info("JDBC result set fetch size: " + statementFetchSize);
202 settings.setJdbcFetchSize(statementFetchSize);
203
204 String releaseModeName = PropertiesHelper.getString( Environment.RELEASE_CONNECTIONS, properties, "auto" );
205 log.info( "Connection release mode: " + releaseModeName );
206 ConnectionReleaseMode releaseMode;
207 if ( "auto".equals(releaseModeName) ) {
208 releaseMode = transactionFactory.getDefaultReleaseMode();
209 }
210 else {
211 releaseMode = ConnectionReleaseMode.parse( releaseModeName );
212 if ( releaseMode == ConnectionReleaseMode.AFTER_STATEMENT && !connections.supportsAggressiveRelease() ) {
213 log.warn( "Overriding release mode as connection provider does not support 'after_statement'" );
214 releaseMode = ConnectionReleaseMode.AFTER_TRANSACTION;
215 }
216 }
217 settings.setConnectionReleaseMode( releaseMode );
218
219 //SQL Generation settings:
220
221 String defaultSchema = properties.getProperty(Environment.DEFAULT_SCHEMA);
222 String defaultCatalog = properties.getProperty(Environment.DEFAULT_CATALOG);
223 if (defaultSchema!=null) log.info("Default schema: " + defaultSchema);
224 if (defaultCatalog!=null) log.info("Default catalog: " + defaultCatalog);
225 settings.setDefaultSchemaName(defaultSchema);
226 settings.setDefaultCatalogName(defaultCatalog);
227
228 Integer maxFetchDepth = PropertiesHelper.getInteger(Environment.MAX_FETCH_DEPTH, properties);
229 if (maxFetchDepth!=null) log.info("Maximum outer join fetch depth: " + maxFetchDepth);
230 settings.setMaximumFetchDepth(maxFetchDepth);
231 int batchFetchSize = PropertiesHelper.getInt(Environment.DEFAULT_BATCH_FETCH_SIZE, properties, 1);
232 log.info("Default batch fetch size: " + batchFetchSize);
233 settings.setDefaultBatchFetchSize(batchFetchSize);
234
235 boolean comments = PropertiesHelper.getBoolean(Environment.USE_SQL_COMMENTS, properties);
236 log.info( "Generate SQL with comments: " + enabledDisabled(comments) );
237 settings.setCommentsEnabled(comments);
238
239 boolean orderUpdates = PropertiesHelper.getBoolean(Environment.ORDER_UPDATES, properties);
240 log.info( "Order SQL updates by primary key: " + enabledDisabled(orderUpdates) );
241 settings.setOrderUpdatesEnabled(orderUpdates);
242
243 boolean orderInserts = PropertiesHelper.getBoolean(Environment.ORDER_INSERTS, properties);
244 log.info( "Order SQL inserts for batching: " + enabledDisabled( orderInserts ) );
245 settings.setOrderInsertsEnabled( orderInserts );
246
247 //Query parser settings:
248
249 settings.setQueryTranslatorFactory( createQueryTranslatorFactory(properties) );
250
251 Map querySubstitutions = PropertiesHelper.toMap(Environment.QUERY_SUBSTITUTIONS, " ,=;:\n\t\r\f", properties);
252 log.info("Query language substitutions: " + querySubstitutions);
253 settings.setQuerySubstitutions(querySubstitutions);
254
255 boolean jpaqlCompliance = PropertiesHelper.getBoolean( Environment.JPAQL_STRICT_COMPLIANCE, properties, false );
256 settings.setStrictJPAQLCompliance( jpaqlCompliance );
257 log.info( "JPA-QL strict compliance: " + enabledDisabled( jpaqlCompliance ) );
258
259 // Second-level / query cache:
260
261 boolean useSecondLevelCache = PropertiesHelper.getBoolean(Environment.USE_SECOND_LEVEL_CACHE, properties, true);
262 log.info( "Second-level cache: " + enabledDisabled(useSecondLevelCache) );
263 settings.setSecondLevelCacheEnabled(useSecondLevelCache);
264
265 boolean useQueryCache = PropertiesHelper.getBoolean(Environment.USE_QUERY_CACHE, properties);
266 log.info( "Query cache: " + enabledDisabled(useQueryCache) );
267 settings.setQueryCacheEnabled(useQueryCache);
268
269 // The cache provider is needed when we either have second-level cache enabled
270 // or query cache enabled. Note that useSecondLevelCache is enabled by default
271 settings.setRegionFactory( createRegionFactory( properties, ( useSecondLevelCache || useQueryCache ) ) );
272
273 boolean useMinimalPuts = PropertiesHelper.getBoolean(
274 Environment.USE_MINIMAL_PUTS, properties, settings.getRegionFactory().isMinimalPutsEnabledByDefault()
275 );
276 log.info( "Optimize cache for minimal puts: " + enabledDisabled(useMinimalPuts) );
277 settings.setMinimalPutsEnabled(useMinimalPuts);
278
279 String prefix = properties.getProperty(Environment.CACHE_REGION_PREFIX);
280 if ( StringHelper.isEmpty(prefix) ) prefix=null;
281 if (prefix!=null) log.info("Cache region prefix: "+ prefix);
282 settings.setCacheRegionPrefix(prefix);
283
284 boolean useStructuredCacheEntries = PropertiesHelper.getBoolean(Environment.USE_STRUCTURED_CACHE, properties, false);
285 log.info( "Structured second-level cache entries: " + enabledDisabled(useStructuredCacheEntries) );
286 settings.setStructuredCacheEntriesEnabled(useStructuredCacheEntries);
287
288 if (useQueryCache) settings.setQueryCacheFactory( createQueryCacheFactory(properties) );
289
290 //SQL Exception converter:
291
292 SQLExceptionConverter sqlExceptionConverter;
293 try {
294 sqlExceptionConverter = SQLExceptionConverterFactory.buildSQLExceptionConverter( dialect, properties );
295 }
296 catch(HibernateException e) {
297 log.warn("Error building SQLExceptionConverter; using minimal converter");
298 sqlExceptionConverter = SQLExceptionConverterFactory.buildMinimalSQLExceptionConverter();
299 }
300 settings.setSQLExceptionConverter(sqlExceptionConverter);
301
302 //Statistics and logging:
303
304 boolean showSql = PropertiesHelper.getBoolean(Environment.SHOW_SQL, properties);
305 if (showSql) log.info("Echoing all SQL to stdout");
306 // settings.setShowSqlEnabled(showSql);
307
308 boolean formatSql = PropertiesHelper.getBoolean(Environment.FORMAT_SQL, properties);
309 // settings.setFormatSqlEnabled(formatSql);
310
311 settings.setSqlStatementLogger( new SQLStatementLogger( showSql, formatSql ) );
312
313 boolean useStatistics = PropertiesHelper.getBoolean(Environment.GENERATE_STATISTICS, properties);
314 log.info( "Statistics: " + enabledDisabled(useStatistics) );
315 settings.setStatisticsEnabled(useStatistics);
316
317 boolean useIdentifierRollback = PropertiesHelper.getBoolean(Environment.USE_IDENTIFIER_ROLLBACK, properties);
318 log.info( "Deleted entity synthetic identifier rollback: " + enabledDisabled(useIdentifierRollback) );
319 settings.setIdentifierRollbackEnabled(useIdentifierRollback);
320
321 //Schema export:
322
323 String autoSchemaExport = properties.getProperty(Environment.HBM2DDL_AUTO);
324 if ( "validate".equals(autoSchemaExport) ) settings.setAutoValidateSchema(true);
325 if ( "update".equals(autoSchemaExport) ) settings.setAutoUpdateSchema(true);
326 if ( "create".equals(autoSchemaExport) ) settings.setAutoCreateSchema(true);
327 if ( "create-drop".equals(autoSchemaExport) ) {
328 settings.setAutoCreateSchema(true);
329 settings.setAutoDropSchema(true);
330 }
331
332 EntityMode defaultEntityMode = EntityMode.parse( properties.getProperty( Environment.DEFAULT_ENTITY_MODE ) );
333 log.info( "Default entity-mode: " + defaultEntityMode );
334 settings.setDefaultEntityMode( defaultEntityMode );
335
336 boolean namedQueryChecking = PropertiesHelper.getBoolean( Environment.QUERY_STARTUP_CHECKING, properties, true );
337 log.info( "Named query checking : " + enabledDisabled( namedQueryChecking ) );
338 settings.setNamedQueryStartupCheckingEnabled( namedQueryChecking );
339
340 // String provider = properties.getProperty( Environment.BYTECODE_PROVIDER );
341 // log.info( "Bytecode provider name : " + provider );
342 // BytecodeProvider bytecodeProvider = buildBytecodeProvider( provider );
343 // settings.setBytecodeProvider( bytecodeProvider );
344
345 return settings;
346
347 }
348
349 protected BytecodeProvider buildBytecodeProvider(String providerName) {
350 if ( "javassist".equals( providerName ) ) {
351 return new org.hibernate.bytecode.javassist.BytecodeProviderImpl();
352 }
353 else if ( "cglib".equals( providerName ) ) {
354 return new org.hibernate.bytecode.cglib.BytecodeProviderImpl();
355 }
356 else {
357 log.debug( "using cglib as bytecode provider by default" );
358 return new org.hibernate.bytecode.cglib.BytecodeProviderImpl();
359 }
360 }
361
362 private int getDatabaseMajorVersion(DatabaseMetaData meta) {
363 try {
364 Method gdbmvMethod = DatabaseMetaData.class.getMethod("getDatabaseMajorVersion", null);
365 return ( (Integer) gdbmvMethod.invoke(meta, null) ).intValue();
366 }
367 catch (NoSuchMethodException nsme) {
368 return 0;
369 }
370 catch (Throwable t) {
371 log.debug("could not get database version from JDBC metadata");
372 return 0;
373 }
374 }
375
376 private static String enabledDisabled(boolean value) {
377 return value ? "enabled" : "disabled";
378 }
379
380 protected QueryCacheFactory createQueryCacheFactory(Properties properties) {
381 String queryCacheFactoryClassName = PropertiesHelper.getString(
382 Environment.QUERY_CACHE_FACTORY, properties, "org.hibernate.cache.StandardQueryCacheFactory"
383 );
384 log.info("Query cache factory: " + queryCacheFactoryClassName);
385 try {
386 return (QueryCacheFactory) ReflectHelper.classForName(queryCacheFactoryClassName).newInstance();
387 }
388 catch (Exception cnfe) {
389 throw new HibernateException("could not instantiate QueryCacheFactory: " + queryCacheFactoryClassName, cnfe);
390 }
391 }
392
393 protected RegionFactory createRegionFactory(Properties properties, boolean cachingEnabled) {
394 String regionFactoryClassName = PropertiesHelper.getString( Environment.CACHE_REGION_FACTORY, properties, null );
395 if ( regionFactoryClassName == null && cachingEnabled ) {
396 String providerClassName = PropertiesHelper.getString( Environment.CACHE_PROVIDER, properties, null );
397 if ( providerClassName != null ) {
398 // legacy behavior, apply the bridge...
399 regionFactoryClassName = RegionFactoryCacheProviderBridge.class.getName();
400 }
401 }
402 if ( regionFactoryClassName == null ) {
403 regionFactoryClassName = DEF_CACHE_REG_FACTORY;
404 }
405 log.info( "Cache region factory : " + regionFactoryClassName );
406 try {
407 return ( RegionFactory ) ReflectHelper.classForName( regionFactoryClassName )
408 .getConstructor( new Class[] { Properties.class } )
409 .newInstance( new Object[] { properties } );
410 }
411 catch ( Exception e ) {
412 throw new HibernateException( "could not instantiate RegionFactory [" + regionFactoryClassName + "]", e );
413 }
414 }
415
416 protected QueryTranslatorFactory createQueryTranslatorFactory(Properties properties) {
417 String className = PropertiesHelper.getString(
418 Environment.QUERY_TRANSLATOR, properties, "org.hibernate.hql.ast.ASTQueryTranslatorFactory"
419 );
420 log.info("Query translator: " + className);
421 try {
422 return (QueryTranslatorFactory) ReflectHelper.classForName(className).newInstance();
423 }
424 catch (Exception cnfe) {
425 throw new HibernateException("could not instantiate QueryTranslatorFactory: " + className, cnfe);
426 }
427 }
428
429 protected BatcherFactory createBatcherFactory(Properties properties, int batchSize) {
430 String batcherClass = properties.getProperty(Environment.BATCH_STRATEGY);
431 if (batcherClass==null) {
432 return batchSize==0 ?
433 (BatcherFactory) new NonBatchingBatcherFactory() :
434 (BatcherFactory) new BatchingBatcherFactory();
435 }
436 else {
437 log.info("Batcher factory: " + batcherClass);
438 try {
439 return (BatcherFactory) ReflectHelper.classForName(batcherClass).newInstance();
440 }
441 catch (Exception cnfe) {
442 throw new HibernateException("could not instantiate BatcherFactory: " + batcherClass, cnfe);
443 }
444 }
445 }
446
447 protected ConnectionProvider createConnectionProvider(Properties properties) {
448 return ConnectionProviderFactory.newConnectionProvider(properties);
449 }
450
451 protected TransactionFactory createTransactionFactory(Properties properties) {
452 return TransactionFactoryFactory.buildTransactionFactory(properties);
453 }
454
455 protected TransactionManagerLookup createTransactionManagerLookup(Properties properties) {
456 return TransactionManagerLookupFactory.getTransactionManagerLookup(properties);
457 }
458
459 private Dialect determineDialect(Properties props, String databaseName, int databaseMajorVersion) {
460 return DialectFactory.buildDialect( props, databaseName, databaseMajorVersion );
461 }
462
463 }