Compare commits

...
Author SHA1 Message Date
rbygrave 74c9c14838 [maven-release-plugin] prepare release avaje-ebeanorm-4.5.6 2015-04-24 23:50:02 +12:00
rbygrave fc53e281e8 #276 - WriteJson not write Transient properties (without using PathProperties) 2015-04-24 23:16:10 +12:00
rbygrave 0c5e092d16 Fix for #277 - please check fetchPaths not null - NPE on OrmQueryDetail.convertManyFetchJoinsToQueryJoins(OrmQueryDetail.java:331) 2015-04-24 22:18:22 +12:00
rbygrave 1260edc8ed Tweak to PrimaryServer to handle empty string defaultServerName values 2015-04-12 18:13:26 +12:00
Rob Bygrave de372bfb89 Merge pull request #270 from Ryszard-Trojnacki/master
Ability to run maven tests with diffrent databases.
2015-04-12 17:46:22 +12:00
rbygrave 2ed5e026e0 #275 - ENH: Add support for @SequenceGenerator initialValue and allocationSize 2015-04-12 12:10:57 +12:00
rbygrave 6c37dc494b Fix for #265 - java.lang.NoClassDefFoundError: Could not initialize class com.avaje.ebean.Ebean 2015-04-11 19:40:39 +12:00
rbygrave 6807499d01 Tweak test for #261 2015-04-11 19:30:05 +12:00
rbygrave cb6faa022b Fix for #260 - Trying to insert() or save() an entity with no @Id results in NPE 2015-04-11 19:16:36 +12:00
rbygrave 3b30388b35 Fix for #258 - Missing Inserts for Large Batches - Ebean 3.2.2 2015-04-11 18:55:08 +12:00
rbygrave db57404e18 #274 - Predicate using a foreign key column sometimes adds an extra join 2015-04-11 18:53:17 +12:00
rbygrave bede89fe13 Test for #269 - ENH: Add support for wildcard / startsWith jar searching 2015-04-11 11:14:48 +12:00
Ryszard-Trojnacki c9fb28b288 Added parameter datasource.default to jUnit (surefire plugin) for
changing default database in maven tests.
2015-04-02 08:29:53 +02:00
Ryszard-Trojnacki a192b0cdbf Ability to override default datasource with java parameter. 2015-04-02 08:23:33 +02:00
Rob Bygrave 0a15cdd7b2 Merge pull request #262 from ForNeVeR/issue-259
Fix issue #259
2015-04-02 11:57:57 +13:00
Rob Bygrave 14c5a1328b Merge pull request #264 from mrock42/master
Add link to Ebean ORM project website
2015-04-02 11:55:54 +13:00
Micah Schehl cac6333c5e Add Link to Ebean ORM project website 2015-03-30 00:28:47 -04:00
Micah Schehl f7d1291a41 Add Link to Ebean ORM Website 2015-03-30 00:25:20 -04:00
Friedrich von Never 151e44ff67 Add test for issue #259. 2015-03-26 23:27:21 +06:00
Friedrich von Never 4f24944ec4 Do not use getPreWhere in case of unparsed SQL.
Fixes #259.
2015-03-26 22:28:29 +06:00
rbygrave c3ccb1afb5 No effective change - reformat code whitespace etc 2015-03-22 22:05:15 +13:00
Rob Bygrave ca8f529629 Merge pull request #257 from Ryszard-Trojnacki/master
Added ability to set alias for root table of sql query and sql command exists and not exists.
2015-03-22 21:48:31 +13:00
Ryszard-Trojnacki 2e03452e2e Added SQL command "EXISTS" and "NOT EXISTS" to Ebean (ExpressionFactory,
ExpressionList).
2015-03-19 10:43:12 +01:00
Ryszard-Trojnacki f78f4ea64a Added ability to set alias for root table of sql query. 2015-03-18 10:28:54 +01:00
rbygrave 81aeca5475 [maven-release-plugin] prepare for next development iteration 2015-02-09 22:08:23 +13:00
rbygrave 36b5b3b817 [maven-release-plugin] prepare release avaje-ebeanorm-4.5.5 2015-02-09 22:08:01 +13:00
Rob Bygrave d83a70a155 Merge pull request #245 from megazord/master
Adds support to multiple methods as event listeners
2015-02-08 07:58:01 +13:00
Megazord c140574eba Adds support to multiple methods as event listeners
Fixes #244
2015-02-07 01:26:03 -02:00
rbygrave 4ab2f0df3c #238 - update to test, still not reproducing yet 2015-02-05 00:09:43 +13:00
rbygrave a447361ea0 #238 - Potential fix for stackOverflow error / recursion issue 2015-02-05 00:06:03 +13:00
rbygrave 8fc3f2da15 #238 - unsuccessful attempt to reproduce issue 238 2015-02-04 23:13:53 +13:00
rbygrave a838bc38c5 No effective change - format code 2015-02-04 21:09:12 +13:00
Rob Bygrave 84e853bcfa Merge pull request #241 from tbee/EventListenerIssue
Exceptions in events are swallowed
2015-02-04 21:06:41 +13:00
Rob Bygrave e2a508aa5e Merge pull request #242 from tbee/ClasspathreaderConfig
ebean.classpathreader is not initialized from ebean.properties
2015-02-04 21:05:42 +13:00
Tom Eugelink e9c66abfe6 ebean.classpathreader is not initialized from ebean.properties 2015-02-03 15:52:23 +01:00
Tom Eugelink 050aad717d Exceptions in events are swallowed 2015-02-03 15:47:58 +01:00
rbygrave 30e02de6f3 Fix for #236 - OptimisticLockException when saving a model the second time with joda DateTime and mysql 5.6 2015-02-03 00:57:58 +13:00
rbygrave b8a7017ffd update README with version 4.5.4 2015-02-03 00:22:57 +13:00
rbygrave da61136465 [maven-release-plugin] prepare for next development iteration 2015-02-03 00:12:19 +13:00
rbygrave 90ec7bafb3 [maven-release-plugin] prepare release avaje-ebeanorm-4.5.4 2015-02-03 00:11:56 +13:00
rbygrave c638ad8d56 Fix for #235: Stored procedures not commited 2015-02-03 00:01:18 +13:00
rbygrave 6a582c0eef No effective change - code format 2015-02-03 00:00:49 +13:00
rbygrave 0551b3066f Fix for #237: find method is returning old entity rather than updated entity in some situations 2015-02-02 23:12:37 +13:00
rbygrave af36c9ec7d No effective change - update TestQueryFindPagedList with better test case error message 2015-02-02 22:03:42 +13:00
rbygrave bd99badb28 No effective change - code cleanup of BindableProperty to remove unused parameter (property name) 2015-02-02 21:43:08 +13:00
rbygrave f7e07633a1 No effective change - format code 2015-02-02 21:30:31 +13:00
rbygrave ace45a038e Fix for #240 - Add support for using Joda LocalDateTime and DateTime with @CreatedTimestamp, @UpdatedTimestamp 2015-02-02 20:39:06 +13:00
rbygrave 36dd2bbee7 Add test for #234 - Not-trimmed values in configuration properties 2015-01-29 22:29:02 +13:00
rbygrave 71e7baff01 #232 - no effective change, format of TransactionManager only 2015-01-17 13:32:13 +13:00
rbygrave ef093b2c6c Fix for #232 - New ebean leaves connections with active transactions 2015-01-17 13:28:34 +13:00
rbygrave 14de498955 bump version in README to 4.5.3 2015-01-13 00:59:42 +13:00
rbygrave bcda8576bc [maven-release-plugin] prepare for next development iteration 2015-01-13 00:48:20 +13:00
rbygrave 0d35c8cb1b [maven-release-plugin] prepare release avaje-ebeanorm-4.5.3 2015-01-13 00:48:00 +13:00
rbygrave 6362634924 Fix for #230 - Delete with OneToOne fails with foreign key constraint violation 2015-01-13 00:43:27 +13:00
rbygrave 676a9c8d6c Fix for #229 - EbeanCacheWarmer timer not canceled 2015-01-12 20:59:32 +13:00
rbygrave b1b9f68b7e Fix for #228 - LazyLoad bug with QueryIterator. Getting EntityNotFoundException("Bean has been deleted - lazy loading failed") 2015-01-12 20:41:37 +13:00
rbygrave 961b219e3e Fix for #227 - Many2Many cache bug, incorrect query plan hash calculation 2015-01-11 20:45:07 +13:00
rbygrave a998c0be55 Fix for #226 - Json - support parsing json with unknown bean properties 2014-12-19 00:01:02 +13:00
rbygrave e0159ccb4f Modify JsonContext added support for toBean() toList() and toObject() using JsonParser 2014-12-18 22:07:47 +13:00
rbygrave 423066ada7 Update README with version 4.5.2 2014-12-18 01:14:05 +13:00
rbygrave c9d968b4b0 [maven-release-plugin] prepare for next development iteration 2014-12-18 00:46:55 +13:00
rbygrave 34be7cc1f1 [maven-release-plugin] prepare release avaje-ebeanorm-4.5.2 2014-12-18 00:46:32 +13:00
rbygrave 54f21836b0 Javadoc update for RawSql 2014-12-18 00:44:58 +13:00
rbygrave 4adf5a9eba Fix for #224 - ENH: Add tableAliasMapping() to RawSqlBuilder ... for better mapping of complex objects with raw sql 2014-12-17 23:58:27 +13:00
rbygrave b6a0653a3c Fix for #223 - RawSql that includes a OneToMany not working 2014-12-17 23:05:53 +13:00
rbygrave c0810a375e No effective change - code formatting 2014-12-17 23:04:59 +13:00
rbygrave 90fe19eb8d cleanup of ebean.properties 2014-12-17 21:04:21 +13:00
rbygrave 43ecfe7c19 Fix for #185 - RawSql parse not working with "order siblings by NAME" 2014-12-17 21:03:53 +13:00
rbygrave 15be5dd4c7 Small tweak to example ebean.properties 2014-12-17 00:54:34 +13:00
rbygrave d54cffacea #222 - SQL Server - OnetoMany with compound key using IN is not legal in mssql 2014-12-17 00:53:42 +13:00
rbygrave 965de6e84d #101 - SQL SERVER - drop.sql does not disable referential integrity 2014-12-17 00:52:31 +13:00
rbygrave 56fd6b90be Rename db columns that clash with sql server keywords etc 2014-12-17 00:49:23 +13:00
rbygrave dd85eba3f0 Rename db columns that clash with sql server keywords etc 2014-12-17 00:32:43 +13:00
rbygrave d1a40d5676 Enable disallowBatchOnCascade per DatabasePlatform (added for SQL Server) 2014-12-17 00:31:29 +13:00
rbygrave b0462bcda6 Enable disallowBatchOnCascade per DatabasePlatform (added for SQL Server) 2014-12-17 00:28:28 +13:00
rbygrave 59428bb214 Rename db columns that clash with sql server keywords etc 2014-12-17 00:20:48 +13:00
rbygrave 73cf3e8511 [maven-release-plugin] prepare for next development iteration 2014-12-13 01:14:36 +13:00
rbygrave beff4465b0 [maven-release-plugin] prepare release avaje-ebeanorm-4.5.1 2014-12-13 01:14:14 +13:00
rbygrave 563cdd3b3b Fix for #221 - ebean 4.4.1 does no longer respects ebean.namingConvention.useForeignKeyPrefix 2014-12-13 01:12:28 +13:00
rbygrave 1bd7b99235 Added @Transactional support for PersistBatch #220 2014-12-12 23:32:41 +13:00
rbygrave bb9d9f7e17 Bump pom to 4.5.1-SNAPSHOT 2014-12-10 03:30:56 +13:00
rbygrave 6bb38f2674 #220 - Refactor Persist batch, add effectively add PersistBatch.INSERT (to ALL and NONE) and allow batching per request (save(), insert(), update(), delete()) 2014-12-10 03:29:44 +13:00
rbygrave bb6571f426 javadoc update, eol characters 2014-12-04 01:47:11 +13:00
rbygrave 1ee679c5ae javadoc update 2014-12-04 01:20:35 +13:00
rbygrave e5264ca69e Update version in README to 4.4.1 2014-12-03 22:35:09 +13:00
rbygrave 3a86be5f3d #219 - Update PagedList javadoc - enable foreground query for getTotalRowCount() when loadRowCount() has not been used 2014-12-03 22:33:35 +13:00
rbygrave 9f71b0df22 [maven-release-plugin] prepare for next development iteration 2014-12-01 23:39:03 +13:00
rbygrave f271ea8a0d [maven-release-plugin] prepare release avaje-ebeanorm-4.4.1 2014-12-01 23:38:42 +13:00
rbygrave 1ced574cd8 bump pom version to 4.4.1-SNAPSHOT 2014-12-01 23:36:41 +13:00
rbygrave 9dd412cd19 #218 - Internal change - modify SequenceIdGenerator to use Long instead of Integer 2014-12-01 23:35:25 +13:00
rbygrave 8237072f5b #217 - ENH: Restore findFutureList() removing its previous deprecated status. Change implementation to use its own PersistenceContext (aka PersistenceContextScope.QUERY) 2014-12-01 23:09:25 +13:00
rbygrave dd30b43d59 #216 - ENH: Add Model.Finder deleteById(id) method 2014-12-01 23:02:58 +13:00
rbygrave 09f8967459 #215 - Remove findPagingList() ... it has been deprecated for a while, please migrate to findPagedList() 2014-12-01 21:52:40 +13:00
rbygrave e3bf3629ee Remove extraneous ServerConfig.enhanceLogLevel which is no longer used 2014-12-01 21:27:27 +13:00
rbygrave b6edd33f9a Fix for #214 - Change default lazyLoadBatchSize from 1 to 10 (in ServerConfig) 2014-12-01 21:16:30 +13:00
rbygrave 3a11a23e01 Fix for #213 - Make PathProperties easier to use - deprecate JsonWriteOptions 2014-12-01 21:10:51 +13:00
rbygrave 370267ab6f Refactor properties bootup, remove GlobalProperties and allow ServerConfig.loadFromProperties() 2014-11-30 17:08:02 +13:00
rbygrave fe65a0ccb9 Change Test - make test more explicit wrt batch lazy loading 2014-11-30 12:55:46 +13:00
rbygrave 2837324a03 Change Test - make test more explicit wrt batch lazy loading 2014-11-30 12:55:16 +13:00
rbygrave a6bd614f87 bump version in readme 2014-11-27 01:20:57 +13:00
rbygrave dfaf4b2f31 [maven-release-plugin] prepare for next development iteration 2014-11-27 01:01:41 +13:00
rbygrave 0f26160e1b [maven-release-plugin] prepare release avaje-ebeanorm-4.3.1 2014-11-27 01:00:53 +13:00
rbygrave cb66aaf66c Change pom to attach javadocs as jar for deploy to central 2014-11-27 00:58:47 +13:00
rbygrave 78b1c05de1 [maven-release-plugin] prepare for next development iteration 2014-11-26 23:54:47 +13:00
rbygrave c70a4f80eb [maven-release-plugin] prepare release avaje-ebeanorm-4.3.0 2014-11-26 23:54:21 +13:00
rbygrave cc788e281f No effective change - javadoc update 2014-11-26 23:53:06 +13:00
rbygrave d55a7b26f8 No effective change - javadoc warnings 2014-11-26 23:08:01 +13:00
rbygrave 2195d7ecbc #211 - ENH: Add support for File as a bean type ... for streaming into/from db 2014-11-26 22:45:33 +13:00
rbygrave 983391af94 #210 - ENH: Add Query.setLazyLoadBatch(int size) method ... for per query control 2014-11-26 20:24:14 +13:00
rbygrave 478053263f #210 - ENH: Add Query.setLazyLoadBatch(int size) method ... for per query control 2014-11-26 20:13:30 +13:00
rbygrave 1cd31e13b9 bump pom to 4.3.0-SNAPSHOT 2014-11-25 23:55:24 +13:00
rbygrave 374d7faae9 bump pom to 4.3.0-SNAPSHOT 2014-11-25 23:54:46 +13:00
rbygrave 6a6cff2236 Fix for #209 - refresh() ... does not refresh OneToMany or ManyToMany properties 2014-11-25 23:40:21 +13:00
rbygrave d5d6ac675b Add support for java.time types for use with @CreatedTimestamp and @UpdatedTimestamp 2014-11-24 20:41:46 +13:00
rbygrave 8ea1f51c7f Remove deprecated Expression.join() methods 2014-11-23 00:53:48 +13:00
rbygrave 6ecb77dcc2 Fix for #206 - Reuse SqlUpdate when binding a list that can vary in size 2014-11-22 23:40:14 +13:00
rbygrave 216806074c Remove PersistenceContextScope.NONE for now 2014-11-22 12:46:40 +13:00
rbygrave 65cb2ae94d #203: ENH: Add Transaction hook ... for preCommit(), postCommit(), postRollback() 2014-11-22 12:14:21 +13:00
rbygrave 6677a6199c Fix for #207: Add support for PeristenceContextScope QUERY and NONE (in addition to the existing TRANSACTION scope) 2014-11-20 00:53:32 +13:00
rbygrave 55eaa0b484 Fix for #204: Revert change from #163 - Add back support for DataSource with autocommit=true 2014-11-19 20:10:50 +13:00
rbygrave 353f662d70 Fix for #205: For JsonContext API wrap checked IOException in runtime JsonIOException 2014-11-19 20:00:31 +13:00
rbygrave c95d76d6f6 Fix for #201: Change BeanPersistListener to use isRegisterFor() method rather than generics type (just like BeanPersistController) 2014-11-19 19:43:34 +13:00
Rob Bygrave e04d6979d1 Merge pull request #202 from ebean-orm/java8
Add support for java.time types, tidy up ScalarTypes
2014-11-19 00:13:37 +13:00
rbygrave b8078083b1 Add MonthDay support, ScalarTypeMonthDay 2014-11-18 23:57:53 +13:00
rbygrave 07cdb69a5b Add Period support via CompoundTypePeriod, rename server/reflect to server/properties 2014-11-18 23:09:26 +13:00
rbygrave 98f6e1e6af Update ScalarTypeMonth and ScalarTypeDayOfWeek 2014-11-18 20:06:13 +13:00
rbygrave fd4d757f87 WIP Java8 types - ScalarTypeDuration etc 2014-11-18 01:20:26 +13:00
rbygrave b71b0fc8e2 WIP Java8 types - ScalarTypeDuration etc 2014-11-17 01:09:43 +13:00
rbygrave 4303bcfd13 No effective change - tidy up on DeployCreateProperties 2014-11-17 00:23:30 +13:00
rbygrave 7f30dc7b1e WIP Java8 types - ScalarTypeDuration etc 2014-11-17 00:05:20 +13:00
rbygrave 4a882b537c #197 - Add Query findEach() findEachWhile() ... as better method names for findVisit(). More consistent with forEach() methods 2014-11-14 21:26:32 +13:00
rbygrave 641385aab5 ScalarTypeLocalDate update - some test ordering issues 2014-11-14 01:01:32 +13:00
rbygrave 33ab7eb7a5 Add ScalarTypeLocalDate 2014-11-13 23:13:51 +13:00
Rob Bygrave f6674f8e3b [maven-release-plugin] prepare for next development iteration 2014-11-13 01:28:18 +13:00
Rob Bygrave 4e8b3f22de [maven-release-plugin] prepare release avaje-ebeanorm-4.2.0 2014-11-13 01:27:58 +13:00
Rob Bygrave 55e43ddc36 Add whitespace for MANIFEST.MF 2014-11-13 01:26:47 +13:00
Rob Bygrave 97d75c06a1 Revert MANIFEST.MF - invalid header 2014-11-13 01:15:47 +13:00
Rob Bygrave adcfa939e4 revert MANIFEST.MF - invalid jackson part 2014-11-13 01:11:07 +13:00
Rob Bygrave 8a4d78a3f5 Fix JSON for ScalarTypePostgresHstore with field name and nulls 2014-11-13 00:49:20 +13:00
Rob Bygrave 04e77d9949 Bump pom to 4.2.0-SNAPSHOT 2014-11-12 23:30:31 +13:00
Rob Bygrave 76e7b8d199 Add to Model the apply(PathProperties) method 2014-11-12 23:06:26 +13:00
Rob Bygrave 00f85a42f8 JSON Refactor - move EJson into text.json package 2014-11-12 22:56:42 +13:00
Rob Bygrave e649a70509 make SimpleTextParser class not public 2014-11-12 22:35:48 +13:00
Rob Bygrave 2ec50d2c71 Add query.apply(PathProperties) 2014-11-12 22:24:37 +13:00
Rob Bygrave eaee3a9c02 JSON refactor - tidy up 2014-11-12 22:10:28 +13:00
Rob Bygrave 221272328c WIP JSON refactor - tidy up ScalarTypes 2014-11-12 00:17:24 +13:00
Rob Bygrave 1677917465 Move from javax.json to jackson-core for parser/generator 2014-11-10 20:31:11 +13:00
Rob Bygrave bfefdbac41 Merge from JSON refactoring branch 2014-11-06 23:32:27 +13:00
Rob Bygrave 58755a51a9 JSON Refactor - compound type support 2014-11-06 23:18:20 +13:00
Rob Bygrave b313809894 WIP json refactor - initial tidy 2014-11-05 23:15:09 +13:00
rbygrave 0f3b1eb812 Tidy up test - TestInfoOneToOne, oneToOne cascade update 2014-10-29 23:45:24 +13:00
rbygrave 306cc4adf4 Add test for oneToOne cascade update 2014-10-29 23:43:11 +13:00
rbygrave ed88978173 No effective change - javadoc tweak on params 2014-10-29 23:41:40 +13:00
rbygrave ae266968b4 [maven-release-plugin] prepare for next development iteration 2014-10-29 00:11:43 +13:00
rbygrave aa86454cc4 [maven-release-plugin] prepare release avaje-ebeanorm-4.1.9 2014-10-29 00:11:20 +13:00
rbygrave c99bec4940 Add First/Second/SuperSecond test 2014-10-29 00:07:25 +13:00
rbygrave 7426274ef6 bump agent to 4.1.9 2014-10-28 23:46:20 +13:00
Rob Bygrave 130b7e3327 Add another self referencing test - SelfParent 2014-10-20 23:06:50 +13:00
Rob Bygrave de201715fc #196 - Add support for using java.net.InetAddress in entity beans 2014-10-20 22:36:18 +13:00
Rob Bygrave 241f6f3d6d #196 - Add support for using java.net.InetAddress in entity beans 2014-10-20 22:31:22 +13:00
Rob Bygrave 1210aca027 Add test for SqlUpdate in various transaction scopes 2014-10-20 20:18:17 +13:00
Rob Bygrave e653c34211 Fix for #188 - ClassPathSearch not searching in running Jar 2014-10-16 23:15:16 +13:00
Rob Bygrave 131750cf85 No effective change - format 2014-10-16 22:58:13 +13:00
Rob Bygrave 17b16fd5d9 Merge pull request #194 from nedge/master
FIX for ManyToOne targetType & OSGI MANIFEST
2014-10-15 19:55:17 +13:00
Eddie Mc Greal 1065a84709 FIX for ManyToOne targetType & OSGI MANIFEST 2014-10-14 08:10:31 +02:00
Rob Bygrave 13791a2e58 Update version in readme 2014-10-08 22:13:48 +13:00
Rob Bygrave 277703d750 [maven-release-plugin] prepare for next development iteration 2014-10-08 21:44:25 +13:00
Rob Bygrave 637cbc6489 [maven-release-plugin] prepare release avaje-ebeanorm-4.1.8 2014-10-08 21:44:15 +13:00
Rob Bygrave 5d09ca16d6 Fix for #192 - Backslash literal in LIKE expression - Postgres (and H2, MySql) behaviour 2014-10-08 21:42:39 +13:00
Rob Bygrave 6ed1f25e22 [maven-release-plugin] prepare for next development iteration 2014-09-10 00:02:41 +12:00
Rob Bygrave 0ceda6406e WIP on JSON refactor 2014-06-28 03:09:10 +12:00
548 changed files with 29699 additions and 18592 deletions
+7 -3
View File
@@ -1,12 +1,16 @@
[![Stories in Ready](https://badge.waffle.io/ebean-orm/avaje-ebeanorm.png?label=ready&title=Ready)](https://waffle.io/ebean-orm/avaje-ebeanorm)
avaje-ebeanorm
Ebean ORM
==============
Project Website
----------------
[http://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
Maven Dependency
----------------
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>4.1.4</version>
<version>4.5.5</version>
</dependency>
+56 -10
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>4.1.7</version>
<version>4.5.6</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
@@ -54,6 +54,12 @@
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
@@ -83,6 +89,13 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-agentloader</artifactId>
@@ -93,14 +106,14 @@
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-agent</artifactId>
<version>4.1.6</version>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.153</version>
<version>1.4.182</version>
<scope>test</scope>
</dependency>
@@ -176,7 +189,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<version>3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
@@ -185,7 +198,7 @@
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>4.1.6</version>
<version>4.5.1</version>
<executions>
<!-- Not going to enhance Model bean -->
<execution>
@@ -215,10 +228,25 @@
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
<systemProperties>
<property>
<!-- transfer datasource.default parameter -->
<name>datasource.default</name>
<value>${datasource.default}</value>
</property>
</systemProperties>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<!--
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
@@ -226,19 +254,37 @@
<configuration>
<doctitle>Ebean 4</doctitle>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
<!-- <excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames> -->
<!--<additionalparam>-Xdoclint:none</additionalparam>-->
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
<docletArtifact>
<groupId>org.avaje</groupId>
<artifactId>pygments-doclet</artifactId>
<version>1.0.0</version>
</docletArtifact>
<additionalparam>
-Xdoclint:none
-include-basedir ${project.basedir}
-attributes "idseparator=-; project_name=${project.name}; \
project_version=${project.version}; \
project_desc=${project.description}"
</additionalparam>
<linksource>true</linksource>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>site</phase>
<goals>
<goal>aggregate</goal>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
-->
</plugins>
</build>
@@ -81,10 +81,6 @@ public interface BeanState {
* {@link EbeanServer#createEntityBean(Class)}, then populate its properties
* and then call this method specifying which properties where loaded or null
* for a fully loaded entity bean.
*
* @param loadedProperties
* the properties that where loaded or null for a fully loaded entity
* bean.
*/
public void setLoaded();
}
@@ -16,8 +16,6 @@ class DRawSqlParser {
public static final String $_WHERE = "${where}";
private static final String ORDER_BY = "order by";
private final SimpleTextParser textParser;
private String sql;
@@ -35,6 +33,7 @@ class DRawSqlParser {
private int groupByPos = -1;
private int havingPos = -1;
private int orderByPos = -1;
private int orderByStmtPos = -1;
private boolean whereExprAnd;
private int whereExprPos = -1;
@@ -66,12 +65,12 @@ class DRawSqlParser {
String preFrom = removeWhitespace(findPreFromSql());
String preWhere = removeWhitespace(findPreWhereSql());
String preHaving = removeWhitespace(findPreHavingSql());
String orderByPrefix = findOrderByPrefixSql();
String orderBySql = findOrderBySql();
preFrom = trimSelectKeyword(preFrom);
return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd,
orderBySql, (distinctPos > -1));
return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1));
}
/**
@@ -142,12 +141,12 @@ class DRawSqlParser {
return preWhereExprSql;
}
private String findOrderByPrefixSql() {
return (orderByPos < 1) ? null : sql.substring(orderByPos, orderByStmtPos);
}
private String findOrderBySql() {
if (orderByPos > -1) {
int pos = orderByPos + ORDER_BY.length();
return sql.substring(pos).trim();
}
return null;
return (orderByStmtPos < 1) ? null : sql.substring(orderByStmtPos).trim();
}
private String findPreHavingSql() {
@@ -226,6 +225,11 @@ class DRawSqlParser {
}
orderByPos = textParser.findWordLower("order", startOrderBy);
if (orderByPos > 1) {
// there might be keywords like siblings in between the order
// and by so search for the by keyword explicitly
orderByStmtPos = 2 + textParser.findWordLower("by", orderByPos);
}
}
private int findWhereExprPosition() {
+363 -309
View File
@@ -15,7 +15,6 @@ import org.slf4j.LoggerFactory;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
@@ -63,34 +62,38 @@ import com.avaje.ebean.text.json.JsonContext;
* created automatically they are configured using information in the
* ebean.properties file.
* </p>
*
* <pre>{@code
*
* // fetch shipped orders (and also their customer)
* List<Order> list = Ebean.find(Order.class)
* .fetch("customer")
* .where()
* .eq("status.code", Order.Status.SHIPPED)
* .findList();
*
* // read/use the order list ...
* for (Order order : list) {
* Customer customer = order.getCustomer();
* ...
* }
*
* }</pre>
*
* <pre class="code">
* // fetch shipped orders (and also their customer)
* List&lt;Order&gt; list = Ebean.find(Order.class)
* .fetch(&quot;customer&quot;)
* .where()
* .eq(&quot;status.code&quot;, Order.Status.SHIPPED)
* .findList();
* <pre>{@code
*
* // fetch order 10, modify and save
* Order order = Ebean.find(Order.class, 10);
*
* // read/use the order list ...
* for (Order order : list) {
* Customer customer = order.getCustomer();
* ...
* }
* </pre>
* OrderStatus shipped = Ebean.getReference(OrderStatus.class,"SHIPPED");
* order.setStatus(shipped);
* order.setShippedDate(shippedDate);
* ...
*
* <pre class="code">
* // fetch order 10, modify and save
* Order order = Ebean.find(Order.class, 10);
*
* OrderStatus shipped = Ebean.getReference(OrderStatus.class,&quot;SHIPPED&quot;);
* order.setStatus(shipped);
* order.setShippedDate(shippedDate);
* ...
*
* // implicitly creates a transaction and commits
* Ebean.save(order);
* </pre>
* // implicitly creates a transaction and commits
* Ebean.save(order);
*
* }</pre>
*
* <p>
* When you have multiple databases and need access to a specific one the
@@ -98,20 +101,22 @@ import com.avaje.ebean.text.json.JsonContext;
* specific database.
* </p>
*
* <pre class="code">
* // Get access to the Human Resources EbeanServer/Database
* EbeanServer hrDb = Ebean.getServer(&quot;hr&quot;);
* <pre> {@code
*
* // Get access to the Human Resources EbeanServer/Database
* EbeanServer hrDb = Ebean.getServer("hr");
*
*
* // fetch contact 3 from the HR database
* Contact contact = hrDb.find(Contact.class, 3);
* // fetch contact 3 from the HR database
* Contact contact = hrDb.find(Contact.class, 3);
*
* contact.setName(&quot;I'm going to change&quot;);
* ...
* contact.setName("I'm going to change");
* ...
*
* // save the contact back to the HR database
* hrDb.save(contact);
* </pre>
* // save the contact back to the HR database
* hrDb.save(contact);
*
* }</pre>
*/
public final class Ebean {
private static final Logger logger = LoggerFactory.getLogger(Ebean.class);
@@ -147,29 +152,28 @@ public final class Ebean {
private ServerManager() {
// skipDefaultServer is set by EbeanServerFactory
// ... when it is creating the primaryServer
if (GlobalProperties.isSkipPrimaryServer()) {
// primary server being created by EbeanServerFactory
// ... so we should not try and create it here
logger.debug("GlobalProperties.isSkipPrimaryServer()");
try {
// skipDefaultServer is set by EbeanServerFactory
// ... when it is creating the primaryServer
if (PrimaryServer.isSkip()) {
// primary server being created by EbeanServerFactory
// ... so we should not try and create it here
logger.debug("PrimaryServer.isSkip()");
} else {
// look to see if there is a default server defined
String primaryName = getPrimaryServerName();
logger.debug("primaryName:" + primaryName);
if (primaryName != null && primaryName.trim().length() > 0) {
primaryServer = getWithCreate(primaryName.trim());
} else {
// look to see if there is a default server defined
String primaryName = PrimaryServer.getPrimaryServerName();
logger.debug("primaryName:" + primaryName);
if (primaryName != null && primaryName.trim().length() > 0) {
primaryServer = getWithCreate(primaryName.trim());
}
}
} catch (RuntimeException e) {
logger.error("Error trying to create the default EbeanServer", e);
throw e;
}
}
private String getPrimaryServerName() {
String serverName = GlobalProperties.get("ebean.default.datasource", null);
return GlobalProperties.get("datasource.default", serverName);
}
private EbeanServer getPrimaryServer() {
if (primaryServer == null) {
String msg = "The default EbeanServer has not been defined?";
@@ -242,12 +246,12 @@ public final class Ebean {
* Ebean.
* </p>
*
* <pre class="code">
* // use the &quot;hr&quot; database
* EbeanServer hrDatabase = Ebean.getServer(&quot;hr&quot;);
* <pre>{@code
* // use the "hr" database
* EbeanServer hrDatabase = Ebean.getServer("hr");
*
* Person person = hrDatabase.find(Person.class, 10);
* </pre>
* }</pre>
*
* @param name
* the name of the server, use null for the 'default server'
@@ -281,7 +285,7 @@ public final class Ebean {
* Register the server with this Ebean singleton. Specify if the registered
* server is the primary/default server.
*/
protected static void register(EbeanServer server, boolean isPrimaryServer) {
public static void register(EbeanServer server, boolean isPrimaryServer) {
serverMgr.register(server, isPrimaryServer);
}
@@ -323,22 +327,24 @@ public final class Ebean {
* etc.
* </p>
*
* <pre class="code">
* // start a transaction (stored in a ThreadLocal)
* Ebean.beginTransaction();
* try {
* Order order = Ebean.find(Order.class,10); ...
* <pre>{@code
*
* // start a transaction (stored in a ThreadLocal)
* Ebean.beginTransaction();
* try {
* Order order = Ebean.find(Order.class,10); ...
*
* Ebean.save(order);
*
* Ebean.save(order);
* Ebean.commitTransaction();
*
* Ebean.commitTransaction();
*
* } finally {
* // rollback if we didn't commit
* // i.e. an exception occurred before commitTransaction().
* Ebean.endTransaction();
* }
* </pre>
* } finally {
* // rollback if we didn't commit
* // i.e. an exception occurred before commitTransaction().
* Ebean.endTransaction();
* }
*
* }</pre>
*
* <p>
* If you want to externalise the transaction management then you should be
@@ -372,6 +378,19 @@ public final class Ebean {
return serverMgr.getPrimaryServer().currentTransaction();
}
/**
* Register a TransactionCallback on the currently active transaction.
* <p/>
* If there is no currently active transaction then a PersistenceException is thrown.
*
* @param transactionCallback the transaction callback to be registered with the current transaction
*
* @throws PersistenceException if there is no currently active transaction
*/
public static void register(TransactionCallback transactionCallback) throws PersistenceException {
serverMgr.getPrimaryServer().register(transactionCallback);
}
/**
* Commit the current transaction.
*/
@@ -397,17 +416,19 @@ public final class Ebean {
* Code example:
* </p>
*
* <pre class="code">
* Ebean.beginTransaction();
* try {
* // do some fetching and or persisting
* // commit at the end Ebean.commitTransaction();
* <pre>{@code
* Ebean.beginTransaction();
* try {
* // do some fetching and or persisting
*
* // commit at the end
* Ebean.commitTransaction();
*
* } finally {
* // if commit didn't occur then rollback the transaction
* Ebean.endTransaction();
* }
* </pre>
* } finally {
* // if commit didn't occur then rollback the transaction
* Ebean.endTransaction();
* }
* }</pre>
*/
public static void endTransaction() {
serverMgr.getPrimaryServer().endTransaction();
@@ -440,15 +461,15 @@ public final class Ebean {
* saving an order will also save all its details.
* </p>
*
* <pre class="code">
* public class Order { ...
* <pre>{@code
* public class Order { ...
*
* &#064;OneToMany(cascade=CascadeType.ALL, mappedBy=&quot;order&quot;)
* &#064;JoinColumn(name=&quot;order_id&quot;)
* List&lt;OrderDetail&gt; details;
* ...
* }
* </pre>
* @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* @JoinColumn(name="order_id")
* List<OrderDetail> details;
* ...
* }
* }</pre>
*
* <p>
* When a save cascades via a OneToMany or ManyToMany Ebean will automatically
@@ -485,16 +506,16 @@ public final class Ebean {
* An unmodified bean that is saved or updated is normally skipped and this marks the bean as
* dirty so that it is not skipped.
*
* <pre class="code">
* <pre>{@code
*
* Customer customer = Ebean.find(Customer, id);
* Customer customer = Ebean.find(Customer, id);
*
* // mark the bean as dirty so that a save() or update() will
* // increment the version property
* Ebean.markAsDirty(customer);
* Ebean.save(customer);
* // mark the bean as dirty so that a save() or update() will
* // increment the version property
* Ebean.markAsDirty(customer);
* Ebean.save(customer);
*
* </pre>
* }</pre>
*/
public static void markAsDirty(Object bean) throws OptimisticLockException {
serverMgr.getPrimaryServer().markAsDirty(bean);
@@ -523,15 +544,15 @@ public final class Ebean {
* properties are included instead.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // A 'stateless update' example
* Customer customer = new Customer();
* customer.setId(7);
* customer.setName(&quot;ModifiedNameNoOCC&quot;);
* ebeanServer.update(customer);
* // A 'stateless update' example
* Customer customer = new Customer();
* customer.setId(7);
* customer.setName("ModifiedNameNoOCC");
* ebeanServer.update(customer);
*
* </pre>
* }</pre>
*
* @see ServerConfig#setUpdatesDeleteMissingChildren(boolean)
* @see ServerConfig#setUpdateChangesOnly(boolean)
@@ -655,7 +676,8 @@ public final class Ebean {
/**
* Refresh the values of a bean.
* <p>
* Note that this does not refresh any OneToMany or ManyToMany properties.
* Note that this resets OneToMany and ManyToMany properties so that if they
* are accessed a lazy load will refresh the many property.
* </p>
*/
public static void refresh(Object bean) {
@@ -665,12 +687,14 @@ public final class Ebean {
/**
* Refresh a 'many' property of a bean.
*
* <pre class="code">
* Order order = ...;
* ...
* // refresh the order details...
* Ebean.refreshMany(order, &quot;details&quot;);
* </pre>
* <pre>{@code
*
* Order order = ...;
* ...
* // refresh the order details...
* Ebean.refreshMany(order, "details");
*
* }</pre>
*
* @param bean
* the entity bean containing the List Set or Map to refresh.
@@ -687,16 +711,18 @@ public final class Ebean {
* This is sometimes described as a proxy (with lazy loading).
* </p>
*
* <pre class="code">
* Product product = Ebean.getReference(Product.class, 1);
* <pre>{@code
*
* Product product = Ebean.getReference(Product.class, 1);
*
* // You can get the id without causing a fetch/lazy load
* Integer productId = product.getId();
* // You can get the id without causing a fetch/lazy load
* Integer productId = product.getId();
*
* // If you try to get any other property a fetch/lazy loading will occur
* // This will cause a query to execute...
* String name = product.getName();
* </pre>
* // If you try to get any other property a fetch/lazy loading will occur
* // This will cause a query to execute...
* String name = product.getName();
*
* }</pre>
*
* @param beanType
* the type of entity bean
@@ -726,23 +752,23 @@ public final class Ebean {
* not invoke a DB query.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // find orders and their customers
* List&lt;Order&gt; list = Ebean.find(Order.class)
* .fetch(&quot;customer&quot;)
* .orderBy(&quot;id&quot;)
* // find orders and their customers
* List<Order> list = Ebean.find(Order.class)
* .fetch("customer")
* .orderBy("id")
* .findList();
*
* // sort by customer name ascending, then by order shipDate
* // ... then by the order status descending
* Ebean.sort(list, &quot;customer.name, shipDate, status desc&quot;);
* // sort by customer name ascending, then by order shipDate
* // ... then by the order status descending
* Ebean.sort(list, "customer.name, shipDate, status desc");
*
* // sort by customer name descending (with nulls low)
* // ... then by the order id
* Ebean.sort(list, &quot;customer.name desc nullsLow, id&quot;);
* // sort by customer name descending (with nulls low)
* // ... then by the order id
* Ebean.sort(list, "customer.name desc nullsLow, id");
*
* </pre>
* }</pre>
*
* @param list
* the list of entity beans
@@ -756,41 +782,43 @@ public final class Ebean {
/**
* Find a bean using its unique id. This will not use caching.
*
* <pre class="code">
* // Fetch order 1
* Order order = Ebean.find(Order.class, 1);
* </pre>
* <pre>{@code
* // Fetch order 1
* Order order = Ebean.find(Order.class, 1);
* }</pre>
*
* <p>
* If you want more control over the query then you can use createQuery() and
* Query.findUnique();
* </p>
*
* <pre class="code">
* // ... additionally fetching customer, customer shipping address,
* // order details, and the product associated with each order detail.
* // note: only product id and name is fetch (its a &quot;partial object&quot;).
* // note: all other objects use &quot;*&quot; and have all their properties fetched.
* <pre>{@code
* // ... additionally fetching customer, customer shipping address,
* // order details, and the product associated with each order detail.
* // note: only product id and name is fetch (its a "partial object").
* // note: all other objects use "*" and have all their properties fetched.
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* query.setId(1);
* query.fetch(&quot;customer&quot;);
* query.fetch(&quot;customer.shippingAddress&quot;);
* query.fetch(&quot;details&quot;);
* Query<Order> query = Ebean.find(Order.class)
* .setId(1)
* .fetch("customer")
* .fetch("customer.shippingAddress")
* .fetch("details")
* .query();
*
* // fetch associated products but only fetch their product id and name
* query.fetch(&quot;details.product&quot;, &quot;name&quot;);
* // fetch associated products but only fetch their product id and name
* query.fetch("details.product", "name");
*
* // traverse the object graph...
* // traverse the object graph...
*
* Order order = query.findUnique();
* Customer customer = order.getCustomer();
* Address shippingAddress = customer.getShippingAddress();
* List&lt;OrderDetail&gt; details = order.getDetails();
* OrderDetail detail0 = details.get(0);
* Product product = detail0.getProduct();
* String productName = product.getName();
* </pre>
* Order order = query.findUnique();
* Customer customer = order.getCustomer();
* Address shippingAddress = customer.getShippingAddress();
* List<OrderDetail> details = order.getDetails();
* OrderDetail detail0 = details.get(0);
* Product product = detail0.getProduct();
* String productName = product.getName();
*
* }</pre>
*
* @param beanType
* the type of entity bean to fetch
@@ -802,7 +830,7 @@ public final class Ebean {
}
/**
* Create a <a href="SqlQuery.html">SqlQuery</a> for executing native sql
* Create a SqlQuery for executing native sql
* query statements.
* <p>
* Note that you can use raw SQL with entity beans, refer to the SqlSelect
@@ -860,15 +888,17 @@ public final class Ebean {
* deployment orm xml file.
* </p>
*
* <pre class="code">
* // Use a namedQuery
* UpdateSql update = Ebean.createNamedSqlUpdate(&quot;update.topic.count&quot;);
* <pre>{@code
*
* // Use a namedQuery
* UpdateSql update = Ebean.createNamedSqlUpdate("update.topic.count");
*
* update.setParameter(&quot;count&quot;, 1);
* update.setParameter(&quot;topicId&quot;, 50);
* update.setParameter("count", 1);
* update.setParameter("topicId", 50);
*
* int modifiedCount = update.execute();
* </pre>
* int modifiedCount = update.execute();
*
* }</pre>
*/
public static SqlUpdate createNamedSqlUpdate(String namedQuery) {
return serverMgr.getPrimaryServer().createNamedSqlUpdate(namedQuery);
@@ -883,12 +913,14 @@ public final class Ebean {
* need to bind required parameters and then execute the query.
* </p>
*
* <pre class="code">
* // example
* Query&lt;Order&gt; query = Ebean.createNamedQuery(Order.class, &quot;new.for.customer&quot;);
* query.setParameter(&quot;customerId&quot;, 23);
* List&lt;Order&gt; newOrders = query.findList();
* </pre>
* <pre>{@code
*
* // example
* Query<Order> query = Ebean.createNamedQuery(Order.class, "new.for.customer");
* query.setParameter("customerId", 23);
* List<Order> newOrders = query.findList();
*
* }</pre>
*
* @param beanType
* the class of entity to be fetched
@@ -911,14 +943,15 @@ public final class Ebean {
* moved to {@link #createNamedQuery(Class, String)}.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* String q = &quot;find order fetch details where status = :st&quot;;
* String q = "find order fetch details where status = :st";
*
* List&lt;Order&gt; newOrders = Ebean.createQuery(Order.class, q)
* .setParameter(&quot;st&quot;, Order.Status.NEW)
* List<Order> newOrders = Ebean.>findOrder.class, q)
* .setParameter("st", Order.Status.NEW)
* .findList();
* </pre>
*
* }</pre>
*
* @param query
* the object query
@@ -943,40 +976,43 @@ public final class Ebean {
* Example named updates:
* </p>
*
* <pre class="code">
* package app.data;
* <pre>{@code
* package app.data;
*
* import ...
* import ...
*
* &#064;NamedUpdates(value = {
* &#064;NamedUpdate( name = &quot;setTitle&quot;,
* isSql = false,
* notifyCache = false,
* update = &quot;update topic set title = :title, postCount = :postCount where id = :id&quot;),
* &#064;NamedUpdate( name = &quot;setPostCount&quot;,
* notifyCache = false,
* update = &quot;update f_topic set post_count = :postCount where id = :id&quot;),
* &#064;NamedUpdate( name = &quot;incrementPostCount&quot;,
* notifyCache = false,
* isSql = false,
* update = &quot;update Topic set postCount = postCount + 1 where id = :id&quot;) })
* &#064;Entity
* &#064;Table(name = &quot;f_topic&quot;)
* public class Topic { ...
* </pre>
* @NamedUpdates(value = {
* @NamedUpdate( name = "setTitle",
* isSql = false,
* notifyCache = false,
* update = "update topic set title = :title, postCount = :postCount where id = :id"),
* @NamedUpdate( name = "setPostCount",
* notifyCache = false,
* update = "update f_topic set post_count = :postCount where id = :id"),
* @NamedUpdate( name = "incrementPostCount",
* notifyCache = false,
* isSql = false,
* update = "update Topic set postCount = postCount + 1 where id = :id") })
* @Entity
* @Table(name = "f_topic")
* public class Topic { ...
*
* }</pre>
*
* <p>
* Example using a named update:
* </p>
*
* <pre class="code">
* Update&lt;Topic&gt; update = Ebean.createNamedUpdate(Topic.class, &quot;setPostCount&quot;);
* update.setParameter(&quot;postCount&quot;, 10);
* update.setParameter(&quot;id&quot;, 3);
* <pre>{@code
*
* Update<Topic> update = Ebean.createNamedUpdate(Topic.class, "setPostCount");
* update.setParameter("postCount", 10);
* update.setParameter("id", 3);
*
* int rows = update.execute();
* System.out.println(&quot;rows updated: &quot; + rows);
* </pre>
* int rows = update.execute();
* System.out.println("rows updated: " + rows);
*
* }</pre>
*/
public static <T> Update<T> createNamedUpdate(Class<T> beanType, String namedUpdate) {
@@ -995,21 +1031,22 @@ public final class Ebean {
* An example:
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // The bean name and properties - &quot;topic&quot;,&quot;postCount&quot; and &quot;id&quot;
* // The bean name and properties - "topic","postCount" and "id"
*
* // will be converted into their associated table and column names
* String updStatement = &quot;update topic set postCount = :pc where id = :id&quot;;
* // will be converted into their associated table and column names
* String updStatement = "update topic set postCount = :pc where id = :id";
*
* Update&lt;Topic&gt; update = Ebean.createUpdate(Topic.class, updStatement);
* Update<Topic> update = Ebean.createUpdate(Topic.class, updStatement);
*
* update.set(&quot;pc&quot;, 9);
* update.set(&quot;id&quot;, 3);
* update.set("pc", 9);
* update.set("id", 3);
*
* int rows = update.execute();
* System.out.println(&quot;rows updated:&quot; + rows);
* </pre>
* int rows = update.execute();
* System.out.println("rows updated:" + rows);
*
* }</pre>
*/
public static <T> Update<T> createUpdate(Class<T> beanType, String ormUpdate) {
@@ -1040,38 +1077,37 @@ public final class Ebean {
* which is was created.
* </p>
*
* <pre class="code">
* // Find order 2 additionally fetching the customer, details and details.product
* // name.
* <pre>{@code
* // Find order 2 additionally fetching the customer, details and details.product
* // name.
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* query.fetch(&quot;customer&quot;);
* query.fetch(&quot;details&quot;);
* query.fetch(&quot;detail.product&quot;, &quot;name&quot;);
* query.setId(2);
* Order order = Ebean.find(Order.class)
* .fetch("customer")
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findUnique();
*
* Order order = query.findUnique();
* // Find order 2 additionally fetching the customer, details and details.product
* // name.
* // Note: same query as above but using the query language
* // Note: using a named query would be preferred practice
*
* // Find order 2 additionally fetching the customer, details and details.product
* // name.
* // Note: same query as above but using the query language
* // Note: using a named query would be preferred practice
* String oql = "find order fetch customer fetch details fetch details.product (name) where id = :orderId ";
*
* String oql = &quot;find order fetch customer fetch details fetch details.product (name) where id = :orderId &quot;;
* Query<Order> query = Ebean.find(Order.class);
* query.setQuery(oql);
* query.setParameter("orderId", 2);
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* query.setQuery(oql);
* query.setParameter(&quot;orderId&quot;, 2);
* Order order = query.findUnique();
*
* Order order = query.findUnique();
* // Using a named query
* Query<Order> query = Ebean.find(Order.class, "with.details");
* query.setParameter("orderId", 2);
*
* // Using a named query
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, &quot;with.details&quot;);
* query.setParameter(&quot;orderId&quot;, 2);
* Order order = query.findUnique();
*
* Order order = query.findUnique();
*
* </pre>
* }</pre>
*
* @param beanType
* the class of entity to be fetched
@@ -1131,19 +1167,21 @@ public final class Ebean {
* Example:
* </p>
*
* <pre class="code">
* // example that uses 'named' parameters
* String s = &quot;UPDATE f_topic set post_count = :count where id = :id&quot;
* <pre>{@code
*
* // example that uses 'named' parameters
* String s = "UPDATE f_topic set post_count = :count where id = :id"
*
* SqlUpdate update = Ebean.createSqlUpdate(s);
* SqlUpdate update = Ebean.createSqlUpdate(s);
*
* update.setParameter(&quot;id&quot;, 1);
* update.setParameter(&quot;count&quot;, 50);
* update.setParameter("id", 1);
* update.setParameter("count", 50);
*
* int modifiedCount = Ebean.execute(update);
* int modifiedCount = Ebean.execute(update);
*
* String msg = &quot;There where &quot; + modifiedCount + &quot;rows updated&quot;;
* </pre>
* String msg = "There where " + modifiedCount + "rows updated";
*
* }</pre>
*
* @param sqlUpdate
* the update sql potentially with bind values
@@ -1164,19 +1202,21 @@ public final class Ebean {
* Example:
* </p>
*
* <pre class="code">
* String sql = &quot;{call sp_order_modify(?,?,?)}&quot;;
* <pre>{@code
*
* String sql = "{call sp_order_modify(?,?,?)}";
*
* CallableSql cs = Ebean.createCallableSql(sql);
* cs.setParameter(1, 27);
* cs.setParameter(2, &quot;SHIPPED&quot;);
* cs.registerOut(3, Types.INTEGER);
* CallableSql cs = Ebean.createCallableSql(sql);
* cs.setParameter(1, 27);
* cs.setParameter(2, "SHIPPED");
* cs.registerOut(3, Types.INTEGER);
*
* Ebean.execute(cs);
* Ebean.execute(cs);
*
* // read the out parameter
* Integer returnValue = (Integer) cs.getObject(3);
* </pre>
* // read the out parameter
* Integer returnValue = (Integer) cs.getObject(3);
*
* }</pre>
*
* @see CallableSql
* @see Ebean#execute(SqlUpdate)
@@ -1192,18 +1232,19 @@ public final class Ebean {
* semantics.
* </p>
*
* <pre class="code">
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* Ebean.execute(scope, new TxRunnable() {
* public void run() {
* User u1 = Ebean.find(User.class, 1);
* ...
*
* }
* });
* </pre>
* <pre>{@code
*
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* Ebean.execute(scope, new TxRunnable() {
* public void run() {
* User u1 = Ebean.find(User.class, 1);
* ...
* }
* });
*
* }</pre>
*/
public static void execute(TxScope scope, TxRunnable r) {
serverMgr.getPrimaryServer().execute(scope, r);
@@ -1216,20 +1257,22 @@ public final class Ebean {
* exception (checked or runtime).
* </p>
*
* <pre class="code">
* Ebean.execute(new TxRunnable() {
* public void run() {
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
* <pre>{@code
*
* Ebean.execute(new TxRunnable() {
* public void run() {
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
* u1.setName(&quot;u1 mod&quot;);
* u2.setName(&quot;u2 mod&quot;);
* u1.setName("u1 mod");
* u2.setName("u2 mod");
*
* Ebean.save(u1);
* Ebean.save(u2);
* }
* });
* </pre>
* Ebean.save(u1);
* Ebean.save(u2);
* }
* });
*
* }</pre>
*/
public static void execute(TxRunnable r) {
serverMgr.getPrimaryServer().execute(r);
@@ -1242,18 +1285,20 @@ public final class Ebean {
* semantics.
* </p>
*
* <pre class="code">
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* Ebean.execute(scope, new TxCallable&lt;String&gt;() {
* public String call() {
* User u1 = Ebean.find(User.class, 1);
* ...
* return u1.getEmail();
* }
* });
* </pre>
* <pre>{@code
*
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* Ebean.execute(scope, new TxCallable<String>() {
* public String call() {
* User u1 = Ebean.find(User.class, 1);
* ...
* return u1.getEmail();
* }
* });
*
* }</pre>
*
*/
public static <T> T execute(TxScope scope, TxCallable<T> c) {
@@ -1271,22 +1316,24 @@ public final class Ebean {
* (and you specify the return type via generics).
* </p>
*
* <pre class="code">
* Ebean.execute(new TxCallable&lt;String&gt;() {
* public String call() {
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
* <pre>{@code
*
* Ebean.execute(new TxCallable<String>() {
* public String call() {
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
* u1.setName(&quot;u1 mod&quot;);
* u2.setName(&quot;u2 mod&quot;);
* u1.setName("u1 mod");
* u2.setName("u2 mod");
*
* Ebean.save(u1);
* Ebean.save(u2);
* Ebean.save(u1);
* Ebean.save(u2);
*
* return u1.getEmail();
* }
* });
* </pre>
* return u1.getEmail();
* }
* });
*
* }</pre>
*/
public static <T> T execute(TxCallable<T> c) {
return serverMgr.getPrimaryServer().execute(c);
@@ -1307,7 +1354,7 @@ public final class Ebean {
* </p>
* <p>
* If there is a transaction then this information is placed into the current
* transactions event information. When the transaction is commited this
* transactions event information. When the transaction is committed this
* information is registered (with the transaction manager). If this
* transaction is rolled back then none of the transaction event information
* registers including the information you put in via this method.
@@ -1335,8 +1382,7 @@ public final class Ebean {
/**
* Return the BeanState for a given entity bean.
* <p>
* This will return null if the bean is not an enhanced (or subclassed) entity
* bean.
* This will return null if the bean is not an enhanced entity bean.
* </p>
*/
public static BeanState getBeanState(Object bean) {
@@ -1383,10 +1429,18 @@ public final class Ebean {
}
/**
* Create a JsonContext that will use the default configuration options.
* Return the JsonContext for reading/writing JSON.
*/
public static JsonContext json() {
return serverMgr.getPrimaryServer().json();
}
/**
* Return the JsonContext for reading/writing JSON.
* @deprecated Please use #json instead.
*/
public static JsonContext createJsonContext() {
return serverMgr.getPrimaryServer().createJsonContext();
return json();
}
}
File diff suppressed because it is too large Load Diff
@@ -1,11 +1,12 @@
package com.avaje.ebean;
import javax.persistence.PersistenceException;
import com.avaje.ebean.common.BootupEbeanManager;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ContainerConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.util.ClassUtil;
import javax.persistence.PersistenceException;
import java.lang.reflect.Constructor;
import java.util.Properties;
/**
* Creates EbeanServer instances.
@@ -27,31 +28,43 @@ import com.avaje.ebean.util.ClassUtil;
*/
public class EbeanServerFactory {
private static BootupEbeanManager serverFactory = createServerFactory();
private static BootupEbeanManager bootupEbeanManager;
/**
* Initialise the container with clustering configuration.
*
* Call this prior to creating any EbeanServer instances or alternatively set the
* ContainerConfig on the ServerConfig when creating the first EbeanServer instance.
*/
public static synchronized void initialiseContainer(ContainerConfig containerConfig) {
getServerFactory(containerConfig);
}
/**
* Create using ebean.properties to configure the server.
*/
public static EbeanServer create(String name) {
public static synchronized EbeanServer create(String name) {
EbeanServer server = serverFactory.createServer(name);
return server;
// construct based on loading properties files
// and if invoked by Ebean then it handles registration
BootupEbeanManager serverFactory = getServerFactory(null);
return serverFactory.createServer(name);
}
/**
* Create using the ServerConfig object to configure the server.
*/
public static EbeanServer create(ServerConfig config) {
public static synchronized EbeanServer create(ServerConfig config) {
if (config.getName() == null) {
throw new PersistenceException("The name is null (it is required)");
}
EbeanServer server = serverFactory.createServer(config);
EbeanServer server = createInternal(config);
if (config.isDefaultServer()) {
GlobalProperties.setSkipPrimaryServer(true);
PrimaryServer.setSkip(true);
}
if (config.isRegister()) {
Ebean.register(server, config.isDefaultServer());
@@ -60,13 +73,45 @@ public class EbeanServerFactory {
return server;
}
private static BootupEbeanManager createServerFactory() {
private static EbeanServer createInternal(ServerConfig config) {
return getServerFactory(config.getContainerConfig()).createServer(config);
}
/**
* Get the BootupEbeanManager initialising it if necessary.
*
* @param containerConfig the configuration controlling clustering communication
*/
private static BootupEbeanManager getServerFactory(ContainerConfig containerConfig) {
if (bootupEbeanManager != null) {
return bootupEbeanManager;
}
if (containerConfig == null) {
// effectively load configuration from ebean.properties
Properties properties = PrimaryServer.getProperties();
containerConfig = new ContainerConfig();
containerConfig.loadFromProperties(properties);
}
bootupEbeanManager = createServerFactory(containerConfig);
return bootupEbeanManager;
}
/**
* Create the container instance using the configuration.
*/
private static BootupEbeanManager createServerFactory(ContainerConfig containerConfig) {
String dflt = "com.avaje.ebeaninternal.server.core.DefaultServerFactory";
String implClassName = System.getProperty("ebean.serverfactory", dflt);
try {
return (BootupEbeanManager) ClassUtil.newInstance(implClassName);
Class<?> cls = Class.forName(implClassName);
Constructor<?> constructor = cls.getConstructor(ContainerConfig.class);
return (BootupEbeanManager) constructor.newInstance(containerConfig);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
@@ -169,6 +169,16 @@ public interface ExpressionFactory {
* In - property has a value in the collection of values.
*/
public Expression in(String propertyName, Collection<?> values);
/**
* Exists expression
*/
public Expression exists(Query<?> subQuery);
/**
* Not exists expression
*/
public Expression notExists(Query<?> subQuery);
/**
* Id Equal to - ID property is equal to the value.
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
@@ -87,6 +89,11 @@ public interface ExpressionList<T> extends Serializable {
*/
public Query<T> setOrderBy(String orderBy);
/**
* Apply the path properties to the query replacing the select and fetch clauses.
*/
public Query<T> apply(PathProperties pathProperties);
/**
* Execute the query iterating over the results.
*
@@ -95,9 +102,25 @@ public interface ExpressionList<T> extends Serializable {
public QueryIterator<T> findIterate();
/**
* Execute the query visiting the results.
*
* @see Query#findVisit(QueryResultVisitor)
* Execute the query process the beans one at a time.
*
* @see Query#findEach(QueryEachConsumer)
*/
public void findEach(QueryEachConsumer<T> consumer);
/**
* Execute the query processing the beans one at a time with the ability to
* stop processing before reading all the beans.
*
* @see Query#findEachWhile(QueryEachWhileConsumer)
*/
public void findEachWhile(QueryEachWhileConsumer<T> consumer);
/**
* Deprecated in favor of #findEachWhile which is functionally exactly the same
* but has a much better name.
*
* @deprecated
*/
public void findVisit(QueryResultVisitor<T> visitor);
@@ -185,25 +208,6 @@ public interface ExpressionList<T> extends Serializable {
*/
public FutureList<T> findFutureList();
/**
* Return a PagingList for this query.
* <p>
* This can be used to break up a query into multiple queries to fetch the
* data a page at a time.
* </p>
* <p>
* This typically works by using a query per page and setting
* {@link Query#setFirstRow(int)} and and {@link Query#setMaxRows(int)} on the
* query. This usually would translate into SQL that uses limit offset, rownum
* or row_number function to limit the result set.
* </p>
*
* @param pageSize
* the number of beans fetched per Page
* @deprecated
*/
public PagingList<T> findPagingList(int pageSize);
/**
* Return a PagedList for this query.
* <p>
@@ -238,22 +242,6 @@ public interface ExpressionList<T> extends Serializable {
*/
public Query<T> select(String properties);
/**
* Specify a property (associated bean) to join and <em>fetch</em> including
* all its properties.
*
* @see Query#join(String)
*/
public Query<T> join(String assocProperties);
/**
* Specify a property (associated bean) to join and <em>fetch</em> with its
* specific properties to include (aka partial object).
*
* @see Query#join(String,String)
*/
public Query<T> join(String assocProperty, String assocProperties);
/**
* Set the first row to fetch.
*
@@ -485,6 +473,16 @@ public interface ExpressionList<T> extends Serializable {
* In - property has a value in the collection of values.
*/
public ExpressionList<T> in(String propertyName, Collection<?> values);
/**
* Exists expression
*/
public ExpressionList<T> exists(Query<?> subQuery);
/**
* Not exists expression
*/
public ExpressionList<T> notExists(Query<?> subQuery);
/**
* Id IN a list of id values.
+30 -7
View File
@@ -1,7 +1,11 @@
package com.avaje.ebean;
import javax.persistence.PersistenceException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* FutureList represents the result of a background query execution that will
@@ -15,13 +19,13 @@ import java.util.concurrent.Future;
* A simple example:
* </p>
*
* <pre class="code">
* <pre>{@code
* // create a query to find all orders
* Query&lt;Order&gt; query = Ebean.find(Order.class);
* Query<Order> query = Ebean.find(Order.class);
*
* // execute the query in a background thread
* // immediately returning the futureList
* FutureList&lt;Order&gt; futureList = query.findFutureList();
* FutureList<Order> futureList = query.findFutureList();
*
* // do something else ...
*
@@ -35,13 +39,11 @@ import java.util.concurrent.Future;
*
* if (!futureList.isCancelled()){
* // wait for the query to finish and return the list
* List&lt;Order&gt; list = futureList.get();
* List<Order> list = futureList.get();
* ...
* }
*
* </pre>
*
* @author rbygrave
* }</pre>
*/
public interface FutureList<T> extends Future<List<T>> {
@@ -50,4 +52,25 @@ public interface FutureList<T> extends Future<List<T>> {
*/
public Query<T> getQuery();
/**
* Same as {@link #get()} but wraps InterruptedException and ExecutionException in the
* unchecked PersistenceException.
*
* @return The query list result
*
* @throws PersistenceException when a InterruptedException or ExecutionException occurs.
*/
public List<T> getUnchecked();
/**
* Same as {@link #get(long, java.util.concurrent.TimeUnit)} but wraps InterruptedException
* and ExecutionException in the unchecked PersistenceException.
*
* @return The query list result
*
* @throws TimeoutException if the wait timed out
* @throws PersistenceException if a InterruptedException or ExecutionException occurs.
*/
public List<T> getUnchecked(long timeout, TimeUnit unit) throws TimeoutException;
}
+177 -34
View File
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -22,11 +24,94 @@ import javax.persistence.MappedSuperclass;
* <p>
* You may choose not use this Model mapped superclass if you don't like the 'Active Record' style
* or if you believe it 'pollutes' your entity beans.
*
*
* <p>
* You can use Dependency Injection like Guice or Spring to construct and wire a EbeanServer instance
* and have that same instance used with this Model and Finder. The way that works is that when the
* DI container creates the EbeanServer instance it can be registered with the Ebean singleton. In this
* way the EbeanServer instance can be injected as per normal Guice / Spring dependency injection and
* that same instance also used to support the Model and Finder active record style.
*
* <p>
* If you choose to use the Model mapped superclass you will probably also chose to additionally add
* a {@link Finder} as a public static field to complete the active record pattern and provide a
* relatively nice clean way to write queries.
*
* <h3>Typical common @MappedSuperclass</h3>
* <pre>{@code
*
* // Typically there is a common base model that has some
* // common properties like the ones below
*
* @MappedSuperclass
* public class BaseModel extends Model {
*
* @Id Long id;
*
* @Version Long version;
*
* @CreatedTimestamp Timestamp whenCreated;
*
* @UpdatedTimestamp Timestamp whenUpdated;
*
* ...
*
* }</pre>
*
* <h3>Extend the Model</h3>
* <pre>{@code
*
* // Extend the mappedSuperclass
*
* @Entity @Table(name="oto_account")
* public class Account extends BaseModel {
*
* // add a static Finder
* // ... with Long being the type of our ID property ...
*
* public static final Finder<Long,Account> find =
* new Finder<Long,Account>(Long.class, Account.class);
*
* String name;
*
* @OneToOne(mappedBy = "account",optional = true)
* User user;
*
* ...
* }
*
* }</pre>
*
* <h3>Modal: save()</h3>
* <pre>{@code
*
* // Active record style ... save(), delete() etc
* Account account = new Account();
* account.setName("AC234");
*
* // save() method inherited from Model
* account.save();
*
* }</pre>
*
* <h3>Finder: find byId</h3>
* <pre>{@code
*
* // find byId
* Account account = Account.find.byId(42);
*
* }</pre>
*
* <h3>Finder: find where</h3>
* <pre>{@code
*
* // find where ...
* List<Account> accounts =
* Account.find
* .where().gt("startDate", lastMonth)
* .findList();
*
* }</pre>
*/
@MappedSuperclass
public abstract class Model {
@@ -103,7 +188,9 @@ public abstract class Model {
* customer.markAsDirty();
* customer.save();
*
* </pre>
* </pre>
*
* @see EbeanServer#markAsDirty(Object)
*/
public void markAsDirty() {
db().markAsDirty(this);
@@ -115,6 +202,8 @@ public abstract class Model {
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see EbeanServer#save(Object)
*/
public void save() {
db().save(this);
@@ -122,6 +211,8 @@ public abstract class Model {
/**
* Update this entity.
*
* @see EbeanServer#update(Object)
*/
public void update() {
db().update(this);
@@ -129,6 +220,8 @@ public abstract class Model {
/**
* Insert this entity.
*
* @see EbeanServer#insert(Object)
*/
public void insert() {
db().insert(this);
@@ -136,6 +229,8 @@ public abstract class Model {
/**
* Delete this entity.
*
* @see EbeanServer#delete(Object)
*/
public void delete() {
db().delete(this);
@@ -164,6 +259,8 @@ public abstract class Model {
/**
* Refreshes this entity from the database.
*
* @see EbeanServer#refresh(Object)
*/
public void refresh() {
db().refresh(this);
@@ -176,11 +273,11 @@ public abstract class Model {
* Typically a Finder is defined as a public static field on an entity bean class to provide a
* nice way to write queries.
*
* @param I
* @param <I>
* The Id type. This is most often a {@link Long} but is also often a {@link UUID} or
* {@link String}.
*
* @param T
*
* @param <T>
* The bean type
*/
public static class Finder<I, T> {
@@ -246,10 +343,14 @@ public abstract class Model {
return new Finder<I, T>(server, idType, type);
}
// Does not exist yet but I think should
// public void deleteById(I id) {
// return db().deleteById(type, id);
// }
/**
* Delete a bean by Id.
* <p>
* Equivalent to {@link EbeanServer#delete(Class, Object)}
*/
public void deleteById(I id) {
db().delete(type, id);
}
/**
* Retrieves all entities of the given type.
@@ -265,7 +366,7 @@ public abstract class Model {
* Retrieves an entity by ID.
*
* <p>
* Equivilent to {@link EbeanServer#find(Class, Object)}
* Equivalent to {@link EbeanServer#find(Class, Object)}
*/
public T byId(I id) {
return db().find(type, id);
@@ -275,7 +376,7 @@ public abstract class Model {
* Creates an entity reference for this ID.
*
* <p>
* Equivilent to {@link EbeanServer#getReference(Class, Object)}
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
*/
public T ref(I id) {
return db().getReference(type, id);
@@ -285,7 +386,7 @@ public abstract class Model {
* Creates a filter for sorting and filtering lists of entities locally without going back to
* the database.
* <p>
* Equivilent to {@link EbeanServer#filter(Class)}
* Equivalent to {@link EbeanServer#filter(Class)}
*/
public Filter<T> filter() {
return db().filter(type);
@@ -294,12 +395,21 @@ public abstract class Model {
/**
* Creates a query.
* <p>
* Equivilent to {@link EbeanServer#find(Class)}
* Equivalent to {@link EbeanServer#find(Class)}
*/
public Query<T> query() {
return db().find(type);
}
/**
* Creates a query applying the path properties to set the select and fetch clauses.
* <p>
* Equivalent to {@link Query#apply(com.avaje.ebean.text.PathProperties)}
*/
public Query<T> apply(PathProperties pathProperties) {
return db().find(type).apply(pathProperties);
}
/**
* Returns the next identity value.
*
@@ -313,18 +423,51 @@ public abstract class Model {
/**
* Executes a query and returns the results as a list of IDs.
* <p>
* Equivilent to {@link Query#findIds()}
* Equivalent to {@link Query#findIds()}
*/
public List<Object> findIds() {
return query().findIds();
}
/**
* Execute the query consuming each bean one at a time.
* <p>
* This is generally used to process large queries where unlike findList
* you do not want to hold all the results in memory at once but instead
* process them one at a time (requiring far less memory).
* </p>
* Equivalent to {@link Query#findEach(QueryEachConsumer)}
*/
public void findEach(QueryEachConsumer<T> consumer) {
query().findEach(consumer);
}
/**
* Execute the query consuming each bean one at a time.
* <p>
* Equivalent to {@link Query#findEachWhile(QueryEachWhileConsumer)}
* <p>
* This is similar to #findEach except that you return boolean
* true to continue processing beans and return false to stop
* processing early.
* </p>
* <p>
* This is generally used to process large queries where unlike findList
* you do not want to hold all the results in memory at once but instead
* process them one at a time (requiring far less memory).
* </p>
* Equivalent to {@link Query#findEachWhile(QueryEachWhileConsumer)}
*/
public void findEachWhile(QueryEachWhileConsumer<T> consumer) {
query().findEachWhile(consumer);
}
/**
* Retrieves all entities of the given type.
* <p>
* The same as {@link #all()}
* <p>
* Equivilent to {@link Query#findList()}
* Equivalent to {@link Query#findList()}
*/
public List<T> findList() {
return query().findList();
@@ -333,7 +476,7 @@ public abstract class Model {
/**
* Returns all the entities of the given type as a set.
* <p>
* Equivilent to {@link Query#findSet()}
* Equivalent to {@link Query#findSet()}
*/
public Set<T> findSet() {
return query().findSet();
@@ -342,7 +485,7 @@ public abstract class Model {
/**
* Retrieves all entities of the given type as a map of objects.
* <p>
* Equivilent to {@link Query#findMap()}
* Equivalent to {@link Query#findMap()}
*/
public Map<?, T> findMap() {
return query().findMap();
@@ -352,7 +495,7 @@ public abstract class Model {
* Executes the query and returns the results as a map of the objects specifying the map key
* property.
* <p>
* Equivilent to {@link Query#findMap(String, Class)}
* Equivalent to {@link Query#findMap(String, Class)}
*/
public <K> Map<K, T> findMap(String keyProperty, Class<K> keyType) {
return query().findMap(keyProperty, keyType);
@@ -362,7 +505,7 @@ public abstract class Model {
* Return a PagedList of all entities of the given type (use where() to specify predicates as
* needed).
* <p>
* Equivilent to {@link Query#findPagedList(int, int)}
* Equivalent to {@link Query#findPagedList(int, int)}
*/
public PagedList<T> findPagedList(int pageIndex, int pageSize) {
return query().findPagedList(pageIndex, pageSize);
@@ -371,7 +514,7 @@ public abstract class Model {
/**
* Executes a find row count query in a background thread.
* <p>
* Equivilent to {@link Query#findFutureRowCount()}
* Equivalent to {@link Query#findFutureRowCount()}
*/
public FutureRowCount<T> findFutureRowCount() {
return query().findFutureRowCount();
@@ -380,7 +523,7 @@ public abstract class Model {
/**
* Returns the total number of entities for this type. *
* <p>
* Equivilent to {@link Query#findRowCount()}
* Equivalent to {@link Query#findRowCount()}
*/
public int findRowCount() {
return query().findRowCount();
@@ -397,7 +540,7 @@ public abstract class Model {
* Explicitly sets a comma delimited list of the properties to fetch on the 'main' entity bean,
* to load a partial object.
* <p>
* Equivilent to {@link Query#select(String)}
* Equivalent to {@link Query#select(String)}
*/
public Query<T> select(String fetchProperties) {
return query().select(fetchProperties);
@@ -406,7 +549,7 @@ public abstract class Model {
/**
* Specifies a path to load including all its properties.
* <p>
* Equivilent to {@link Query#fetch(String)}
* Equivalent to {@link Query#fetch(String)}
*/
public Query<T> fetch(String path) {
return query().fetch(path);
@@ -416,7 +559,7 @@ public abstract class Model {
* Additionally specifies a <code>FetchConfig</code> to specify a 'query join' and/or define the
* lazy loading query.
* <p>
* Equivilent to {@link Query#fetch(String, FetchConfig)}
* Equivalent to {@link Query#fetch(String, FetchConfig)}
*/
public Query<T> fetch(String path, FetchConfig joinConfig) {
return query().fetch(path, joinConfig);
@@ -426,7 +569,7 @@ public abstract class Model {
* Specifies a path to fetch with a specific list properties to include, to load a partial
* object.
* <p>
* Equivilent to {@link Query#fetch(String, String)}
* Equivalent to {@link Query#fetch(String, String)}
*/
public Query<T> fetch(String path, String fetchProperties) {
return query().fetch(path, fetchProperties);
@@ -436,7 +579,7 @@ public abstract class Model {
* Additionally specifies a <code>FetchConfig</code> to use a separate query or lazy loading to
* load this path.
* <p>
* Equivilent to {@link Query#fetch(String, String, FetchConfig)}
* Equivalent to {@link Query#fetch(String, String, FetchConfig)}
*/
public Query<T> fetch(String assocProperty, String fetchProperties, FetchConfig fetchConfig) {
return query().fetch(assocProperty, fetchProperties, fetchConfig);
@@ -446,7 +589,7 @@ public abstract class Model {
* Adds expressions to the <code>where</code> clause with the ability to chain on the
* <code>ExpressionList</code>.
* <p>
* Equivilent to {@link Query#where()}
* Equivalent to {@link Query#where()}
*/
public ExpressionList<T> where() {
return query().where();
@@ -458,7 +601,7 @@ public abstract class Model {
* <p>
* This is exactly the same as {@link #orderBy}.
* <p>
* Equivilent to {@link Query#order()}
* Equivalent to {@link Query#order()}
*/
public OrderBy<T> order() {
return query().order();
@@ -480,7 +623,7 @@ public abstract class Model {
* <p>
* This is exactly the same as {@link #order}.
* <p>
* Equivilent to {@link Query#orderBy()}
* Equivalent to {@link Query#orderBy()}
*/
public OrderBy<T> orderBy() {
return query().orderBy();
@@ -499,7 +642,7 @@ public abstract class Model {
/**
* Sets the first row to return for this query.
* <p>
* Equivilent to {@link Query#setFirstRow(int)}
* Equivalent to {@link Query#setFirstRow(int)}
*/
public Query<T> setFirstRow(int firstRow) {
return query().setFirstRow(firstRow);
@@ -508,7 +651,7 @@ public abstract class Model {
/**
* Sets the maximum number of rows to return in the query.
* <p>
* Equivilent to {@link Query#setMaxRows(int)}
* Equivalent to {@link Query#setMaxRows(int)}
*/
public Query<T> setMaxRows(int maxRows) {
return query().setMaxRows(maxRows);
@@ -521,7 +664,7 @@ public abstract class Model {
* Use this to perform a find byId query but with additional control over the query such as
* using select and fetch to control what parts of the object graph are returned.
* <p>
* Equivilent to {@link Query#setId(Object)}
* Equivalent to {@link Query#setId(Object)}
*/
public Query<T> setId(Object id) {
return query().setId(id);
@@ -530,7 +673,7 @@ public abstract class Model {
/**
* Create and return a new query using the OQL.
* <p>
* Equivilent to {@link EbeanServer#createQuery(Class, String)}
* Equivalent to {@link EbeanServer#createQuery(Class, String)}
*/
public Query<T> setQuery(String oql) {
return db().createQuery(type, oql);
@@ -539,7 +682,7 @@ public abstract class Model {
/**
* Create and return a new query based on the <code>RawSql</code>.
* <p>
* Equivilent to {@link Query#setRawSql(RawSql)}
* Equivalent to {@link Query#setRawSql(RawSql)}
*/
public Query<T> setRawSql(RawSql rawSql) {
return query().setRawSql(rawSql);
-74
View File
@@ -1,74 +0,0 @@
package com.avaje.ebean;
import java.util.List;
/**
* Represents a Page of results that is part of a PagingList.
* <p>
* Typically a Page represents the data that is shown to the user at a single
* time - and the user 'pages' through a large list.
* </p>
*
* @author rbygrave
*
* @param <T>
* the entity bean type
*
* @see Query#findPagingList(int)
* @see PagingList
*/
public interface Page<T> {
/**
* Return the list of entities for this page.
*/
public List<T> getList();
/**
* Return the total row count for all pages.
*/
public int getTotalRowCount();
/**
* Return the total number of pages.
*/
public int getTotalPageCount();
/**
* Return the index position of this page.
*/
public int getPageIndex();
/**
* Return true if there is a next page.
*/
public boolean hasNext();
/**
* Return true if there is a previous page.
*/
public boolean hasPrev();
/**
* Return the next page.
*/
public Page<T> next();
/**
* Return the previous page.
*/
public Page<T> prev();
/**
* Helper method to return a "X to Y of Z" string for this page where X is the
* first row, Y the last row and Z the total row count.
*
* @param to
* String to put between the first and last row
* @param of
* String to put between the last row and the total row count
*
* @return String of the format XtoYofZ.
*/
public String getDisplayXtoYofZ(String to, String of);
}
+115 -3
View File
@@ -16,7 +16,44 @@ import java.util.concurrent.Future;
* the query. This translates into SQL that uses limit offset, rownum or row_number function to
* limit the result set.
* </p>
*
*
* <h4>Example: typical use including total row count</h4>
* <pre>{@code
*
* // We want to find the first 100 new orders
* // ... 0 means first page
* // ... page size is 100
*
* PagedList<Order> pagedList
* = ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findPagedList(0, 100);
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* <h4>Example: No total row count required</h4>
* <pre>{@code
*
* // If you are not getting the 'first page' often
* // you do not bother getting the total row count again
* // so instead just get the page list of data
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* }</pre>
*
* @param <T>
* the entity bean type
*
@@ -26,12 +63,53 @@ public interface PagedList<T> {
/**
* Initiate the loading of the total row count in the background.
* <pre>{@code
*
* // initiate the loading of the total row count
* // in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* <p>
* Also note that using loadRowCount() and getTotalRowCount() rather than getFutureRowCount()
* means that exceptions ExecutionException, InterruptedException, TimeoutException are instead
* wrapped in the unchecked PersistenceException (which might be preferrable).
* </p>
*/
public void loadRowCount();
/**
* Return the Future row count. You might get this if you wish to cancel the total row count query
* or specify a timeout for that query.
* or specify a timeout for the row count query.
* <p>
* The loadRowCount() & getTotalRowCount() methods internally make use of this getFutureRowCount() method.
* Generally I expect people to prefer loadRowCount() & getTotalRowCount() over getFutureRowCount().
* </p>
* <pre>{@code
*
* // initiate the row count query in the background thread
* Future<Integer> rowCount = pagedList.getFutureRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // now get the total count with a timeout
* Integer totalRowCount = rowCount.get(30, TimeUnit.SECONDS);
*
* // or ge the total count without a timeout
* Integer totalRowCountViaFuture = rowCount.get();
*
* // which is actually the same as ...
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*/
public Future<Integer> getFutureRowCount();
@@ -42,11 +120,37 @@ public interface PagedList<T> {
/**
* Return the total row count for all pages.
* <p>
* If loadRowCount() has already been called then the row count query is already executing in a background thread
* and this gets the associated Future and gets the value waiting for the future to finish.
* </p>
* <p>
* If loadRowCount() has not been called then this executes the find row count query and returns the result and this
* will just occur in the current thread and not use a background thread.
* </p>
* <pre>{@code
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (which was being executed
* // in a background thread if loadRowCount() was used)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*/
public int getTotalRowCount();
/**
* Return the total number of pages based on the page size and total row count.
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*/
public int getTotalPageCount();
@@ -57,6 +161,10 @@ public interface PagedList<T> {
/**
* Return true if there is a next page.
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*/
public boolean hasNext();
@@ -68,7 +176,11 @@ public interface PagedList<T> {
/**
* Helper method to return a "X to Y of Z" string for this page where X is the first row, Y the
* last row and Z the total row count.
*
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*
* @param to
* String to put between the first and last row
* @param of
@@ -1,127 +0,0 @@
package com.avaje.ebean;
import java.util.List;
import java.util.concurrent.Future;
/**
* Used to page through a query result rather than fetching all the results in a
* single query.
* <p>
* Has the ability to use background threads to 'fetch ahead' the next page and
* get the total row count.
* </p>
* <p>
* If you are building a stateless web application and not keeping the
* PagingList over multiple requests then there is not much to be gained in
* using PagingList. Instead you can just use {@link Query#setFirstRow(int)} and
* {@link Query#setMaxRows(int)}.
* </p>
*
* <p>
* If you are using PagingList is a stateful web application where the
* PagingList is held over multiple requests then PagingList provides the extra
* benefits of
* <ul>
* <li>Fetch ahead - automatically fetching the next page via background query
* execution</li>
* <li>Automatic propagation of the persistence context</li>
* </ul>
* </p>
* <p>
* So with PagingList when you use Page 2 it can automatically fetch Page 3 data
* in the background (using a findFutureList() query). It also automatically
* propagates the persistence context so that all the queries executed by the
* PagingList all use the same persistence context.
* </p>
*
* <pre>
* PagingList&lt;TOne&gt; pagingList =
* Ebean.find(TOne.class)
* .where().gt(&quot;name&quot;, &quot;2&quot;)
* .findPagingList(10);
*
* // get the row count in the background...
* // ... otherwise it is fetched on demand
* // ... when getRowCount() or getPageCount()
* // ... is called
* pagingList.getFutureRowCount();
*
* // get the first page
* Page&lt;TOne&gt; page = pagingList.getPage(0);
*
* // get the beans from the page as a list
* List&lt;TOne&gt; list = page.getList();
* </pre>
*
* @author rbygrave
*
* @param <T>
* the entity bean type
*/
public interface PagingList<T> {
/**
* Refresh will clear all the pages and row count forcing them to be
* re-fetched when next required.
*/
public void refresh();
// public void fetchAll();
// public String? getOrderBy();
// public void setOrderBy(String?);
/**
* By default fetchAhead is true so use this to turn off fetchAhead.
* <p>
* Set this to false if you don't want to fetch ahead using background
* fetching.
* <p>
* If set to true (or left as to default) then the next page is fetched in the
* background as soon as the list is accessed.
* </p>
*/
public PagingList<T> setFetchAhead(boolean fetchAhead);
/**
* Return the Future for getting the total row count.
*/
public Future<Integer> getFutureRowCount();
/**
* Return the data for all the pages in the form of a single List.
* <p>
* Iterating through this list will automatically fire the paging queries as
* required.
* </p>
*/
public List<T> getAsList();
/**
* Return the page size. This is the number of rows per page.
*/
public int getPageSize();
/**
* Return the total row count.
* <p>
* This gets the result from getFutureRowCount and will wait until that query
* has completed.
* </p>
*/
public int getTotalRowCount();
/**
* Return the total page count.
* <p>
* This is based on the total row count. This will wait until the row count
* has returned if it has not already.
* </p>
*/
public int getTotalPageCount();
/**
* Return the page for a given page position (starting at 0).
*/
public Page<T> getPage(int i);
}
@@ -0,0 +1,35 @@
package com.avaje.ebean;
/**
* Defines the scope for PersistenceContext.
* <p/>
* Ebean has traditionally used Transaction scope for the PersistenceContext. This is used to change the scope to
* use (by default) and explicitly set the scope to use for an individual query.
*
* @see com.avaje.ebean.config.ServerConfig#setPersistenceContextScope(PersistenceContextScope)
* @see com.avaje.ebean.Query#setPersistenceContextScope(PersistenceContextScope)
*/
public enum PersistenceContextScope {
/**
* PersistenceContext is scoped to the transaction.
* <p/>
* If a transaction spans 2 or more queries that fetch the same bean in terms of same type
* and same Id value then they share the same bean instance.
* <p/>
* You may want to change to use QUERY scope when you want a query executing in a transaction to effectively
* ignore beans that have already been loaded (by other queries in the same transaction) and instead get a
* 'fresh copy' of the bean.
*/
TRANSACTION,
/**
* PersistenceContext is scoped to the query.
* <p/>
* This means that for this query running in an existing transaction then it will effectively ignore any beans
* that have already been queried/loaded by prior queries in the same transaction.
* <p/>
* You may use QUERY scope on a query that is executed in a transaction and you want to get a 'fresh copy' of the bean.
*/
QUERY
}
@@ -0,0 +1,80 @@
package com.avaje.ebean;
import com.avaje.ebean.config.PropertyMap;
import java.util.Properties;
/**
* Provides singleton state for the default server.
* <p/>
* Intended for internal use as part of bootup, construction, registration of the default server.
*/
class PrimaryServer {
private static Properties globalProperties;
private static String defaultServerName;
private static boolean skip;
/**
* Set whether to skip automatically creating the primary server.
*/
static synchronized void setSkip(boolean skip) {
PrimaryServer.skip = skip;
}
/**
* Return true to skip automatically creating the primary server.
*/
static synchronized boolean isSkip() {
return skip;
}
/**
* Return the default server name.
*/
static synchronized String getPrimaryServerName() {
getProperties();
return defaultServerName;
}
/**
* Return the default configuration Properties.
*/
static synchronized Properties getProperties() {
if (globalProperties == null) {
globalProperties = PropertyMap.defaultProperties();
}
if (defaultServerName == null) {
defaultServerName = determineDefaultServerName();
}
return globalProperties;
}
/**
* Determine and return the default server name checking system environment variables and then global properties.
*/
private static String determineDefaultServerName() {
String defaultServerName = System.getProperty("datasource.default");
if (isEmpty(defaultServerName)) {
defaultServerName = System.getProperty("ebean.default.datasource");
}
if (isEmpty(defaultServerName)) {
defaultServerName = globalProperties.getProperty("datasource.default");
}
if (isEmpty(defaultServerName)) {
defaultServerName = globalProperties.getProperty("ebean.default.datasource");
}
return defaultServerName;
}
/**
* Return true if the string is null or empty.
*/
private static boolean isEmpty(String value) {
return value == null || value.trim().length() == 0;
}
}
+392 -190
View File
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
@@ -11,54 +13,57 @@ import java.util.Set;
* Example: Create the query using the API.
* </p>
*
* <pre class="code">
* List&lt;Order&gt; orderList =
* Ebean.find(Order.class)
* .fetch(&quot;customer&quot;)
* .fetch(&quot;details&quot;)
* <pre>{@code
*
* List<Order> orderList =
* ebeanServer.find(Order.class)
* .fetch("customer")
* .fetch("details")
* .where()
* .like(&quot;customer.name&quot;,&quot;rob%&quot;)
* .gt(&quot;orderDate&quot;,lastWeek)
* .orderBy(&quot;customer.id, id desc&quot;)
* .like("customer.name","rob%")
* .gt("orderDate",lastWeek)
* .orderBy("customer.id, id desc")
* .setMaxRows(50)
* .findList();
*
* ...
* </pre>
* }</pre>
*
* <p>
* Example: The same query using the query language
* </p>
*
* <pre class="code">
* <pre>{@code
*
* String oql =
* &quot; find order &quot;
* +&quot; fetch customer &quot;
* +&quot; fetch details &quot;
* +&quot; where customer.name like :custName and orderDate &gt; :minOrderDate &quot;
* +&quot; order by customer.id, id desc &quot;
* +&quot; limit 50 &quot;;
* " find order "
* +" fetch customer "
* +" fetch details "
* +" where customer.name like :custName and orderDate > :minOrderDate "
* +" order by customer.id, id desc "
* +" limit 50 ";
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, oql);
* query.setParameter(&quot;custName&quot;, &quot;Rob%&quot;);
* query.setParameter(&quot;minOrderDate&quot;, lastWeek);
* Query<Order> query = ebeanServer.createQuery(Order.class, oql);
* query.setParameter("custName", "Rob%");
* query.setParameter("minOrderDate", lastWeek);
*
* List&lt;Order&gt; orderList = query.findList();
* List<Order> orderList = query.findList();
* ...
* </pre>
* }</pre>
*
* <p>
* Example: Using a named query called "with.cust.and.details"
* </p>
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createNamedQuery(Order.class,&quot;with.cust.and.details&quot;);
* query.setParameter(&quot;custName&quot;, &quot;Rob%&quot;);
* query.setParameter(&quot;minOrderDate&quot;, lastWeek);
* <pre>{@code
*
* Query<Order> query = ebeanServer.createNamedQuery(Order.class,"with.cust.and.details");
* query.setParameter("custName", "Rob%");
* query.setParameter("minOrderDate", lastWeek);
*
* List&lt;Order&gt; orderList = query.findList();
* List<Order> orderList = query.findList();
* ...
* </pre>
* }</pre>
*
* <h3>Autofetch</h3>
* <p>
@@ -96,13 +101,13 @@ import java.util.Set;
* Refer to "ALL Properties/Columns" mode of Optimistic Concurrency checking.
* </p>
*
* <pre class="code">
* <pre>{@code
* [ find {bean type} [ ( * | {fetch properties} ) ] ]
* [ fetch {associated bean} [ ( * | {fetch properties} ) ] ]
* [ where {predicates} ]
* [ order by {order by properties} ]
* [ limit {max rows} [ offset {first row} ] ]
* </pre>
* }</pre>
*
* <p>
* <b>FIND</b> <b>{bean type}</b> [ ( <i>*</i> | <i>{fetch properties}</i> ) ]
@@ -158,17 +163,17 @@ import java.util.Set;
* Find orders fetching all its properties
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* </pre>
* }</pre>
*
* <p>
* Find orders fetching all its properties
* </p>
*
* <pre class="code">
* <pre>{@code
* find order (*)
* </pre>
* }</pre>
*
* <p>
* Find orders fetching its id, shipDate and status properties. Note that the id
@@ -176,30 +181,30 @@ import java.util.Set;
* properties.
* </p>
*
* <pre class="code">
* <pre>{@code
* find order (shipDate, status)
* </pre>
* }</pre>
*
* <p>
* Find orders with a named bind variable (that will need to be bound via
* {@link Query#setParameter(String, Object)}).
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* where customer.name like :custLike
* </pre>
* }</pre>
*
* <p>
* Find orders and also fetch the customer with a named bind parameter. This
* will fetch and populate both the order and customer objects.
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* fetch customer
* where customer.id = :custId
* </pre>
* }</pre>
*
* <p>
* Find orders and also fetch the customer, customer shippingAddress, order
@@ -210,13 +215,13 @@ import java.util.Set;
* populated.
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* fetch customer (name)
* fetch customer.shippingAddress
* fetch details
* fetch details.product (sku, name)
* </pre>
* }</pre>
*
* <h3>Early parsing of the Query</h3>
* <p>
@@ -299,6 +304,21 @@ public interface Query<T> extends Serializable {
*/
public Query<T> copy();
/**
* Specify the PersistenceContextScope to use for this query.
* <p/>
* When this is not set the 'default' configured on {@link com.avaje.ebean.config.ServerConfig#setPersistenceContextScope(PersistenceContextScope)}
* is used - this value defaults to {@link com.avaje.ebean.PersistenceContextScope#TRANSACTION}.
* <p/>
* Note that the same persistence Context is used for subsequent lazy loading and query join queries.
* <p/>
* Note that #findEach uses a 'per object graph' PersistenceContext so this scope is ignored for
* queries executed as #findIterate, #findEach, #findEachWhile.
*
* @param scope The scope to use for this query and subsequent lazy loading.
*/
public Query<T> setPersistenceContextScope(PersistenceContextScope scope);
/**
* Return the ExpressionFactory used by this query.
*/
@@ -323,22 +343,37 @@ public interface Query<T> extends Serializable {
*/
public Query<T> setAutofetch(boolean autofetch);
/**
* Set the default lazy loading batch size to use.
* <p>
* When lazy loading is invoked on beans loaded by this query then this sets the
* batch size used to load those beans.
*
* @param lazyLoadBatchSize the number of beans to lazy load in a single batch
*/
public Query<T> setLazyLoadBatchSize(int lazyLoadBatchSize);
/**
* Explicitly set a comma delimited list of the properties to fetch on the
* 'main' entity bean (aka partial object). Note that '*' means all
* 'main' root level entity bean (aka partial object). Note that '*' means all
* properties.
*
* <pre class="code">
* Query&lt;Customer&gt; query = Ebean.createQuery(Customer.class);
*
* // Only fetch the customer id, name and status.
* // This is described as a &quot;Partial Object&quot;
* query.select(&quot;name, status&quot;);
* query.where(&quot;lower(name) like :custname&quot;).setParameter(&quot;custname&quot;, &quot;rob%&quot;);
*
* List&lt;Customer&gt; customerList = query.findList();
* </pre>
*
* <p>
* You use {@link #fetch(String, String)} to specify specific properties to fetch
* on other non-root level paths of the object graph.
* </p>
*
* <pre>{@code
*
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* // Only fetch the customer id, name and status.
* // This is described as a "Partial Object"
* .select("name, status")
* .where.ilike("name", "rob%")
* .findList();
*
* }</pre>
*
* @param fetchProperties
* the properties to fetch for this bean (* = all properties).
*/
@@ -356,31 +391,35 @@ public interface Query<T> extends Serializable {
* "Partial Object" - a bean that only has some of its properties populated.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // query orders...
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* List<Order> orders =
* ebeanserver.find(Order.class)
* // fetch the customer...
* // ... getting the customers name and phone number
* .fetch("customer", "name, phoneNumber")
*
* // fetch the customer...
* // ... getting the customer's name and phone number
* query.fetch(&quot;customer&quot;, &quot;name, phNumber&quot;);
*
* // ... also fetch the customers billing address (* = all properties)
* query.fetch(&quot;customer.billingAddress&quot;, &quot;*&quot;);
* </pre>
* // ... also fetch the customers billing address (* = all properties)
* .fetch("customer.billingAddress", "*")
* .findList();
* }</pre>
*
* <p>
* If columns is null or "*" then all columns/properties for that path are
* fetched.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // fetch customers (their id, name and status)
* Query&lt;Customer&gt; query = Ebean.createQuery(Customer.class);
*
* // only fetch some of the properties of the customers
* query.select(&quot;name, status&quot;);
* List&lt;Customer&gt; list = query.findList();
* </pre>
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName,lastName,email")
* .findList();
*
* }</pre>
*
* @param path
* the path of an associated (1-1,1-M,M-1,M-M) bean.
@@ -393,6 +432,17 @@ public interface Query<T> extends Serializable {
/**
* Additionally specify a FetchConfig to use a separate query or lazy loading
* to load this path.
*
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName,lastName,email", new FetchConfig().lazy(10))
* .findList();
*
* }</pre>
*/
public Query<T> fetch(String assocProperty, String fetchProperties, FetchConfig fetchConfig);
@@ -401,7 +451,17 @@ public interface Query<T> extends Serializable {
* <p>
* The same as {@link #fetch(String, String)} with the fetchProperties as "*".
* </p>
*
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* // eager fetch the contacts
* .fetch("contacts")
* .findList();
*
* }</pre>
*
* @param path
* the property of an associated (1-1,1-M,M-1,M-M) bean.
*/
@@ -410,9 +470,29 @@ public interface Query<T> extends Serializable {
/**
* Additionally specify a JoinConfig to specify a "query join" and or define
* the lazy loading query.
*
*
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* // lazy fetch contacts with a batch size of 100
* .fetch("contacts", new FetchConfig().lazy(100))
* .findList();
*
* }</pre>
*/
public Query<T> fetch(String path, FetchConfig joinConfig);
/**
* Apply the path properties replacing the select and fetch clauses.
* <p>
* This is typically used when the PathProperties is applied to both the query and the JSON output.
* </p>
*/
public Query<T> apply(PathProperties pathProperties);
/**
* Execute the query returning the list of Id's.
* <p>
@@ -431,49 +511,119 @@ public interface Query<T> extends Serializable {
* (typically in a finally block).
* </p>
* <p>
* findEach() and findEachWhile() are preferred to findIterate() as they ensure
* the jdbc statement and resultSet are closed at the end of the iteration.
* </p>
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*/
public QueryIterator<T> findIterate();
/**
* This is deprecated in favor of #findEachWhile.
* <p>
* This is functionally exactly the same as #findEachWhile. It is
* replaced by findEachWhile because the method name is much better.
* </p>
*
* @param visitor
* the visitor used to process the queried beans.
*
* @deprecated
*/
public void findVisit(QueryResultVisitor<T> visitor);
/**
* Execute the query processing the beans one at a time.
* <p>
* This method is appropriate to process very large query results as the
* beans are consumed one at a time and do not need to be held in memory
* (unlike #findList #findSet etc)
* </p>
* <p>
* Note that internally Ebean can inform the JDBC driver that it is expecting larger
* resultSet and specifically for MySQL this hint is required to stop it's JDBC driver
* from buffering the entire resultSet. As such, for smaller resultSets findList() is
* generally preferable.
* </p>
* <p>
* Compared with #findEachWhile this will always process all the beans where as
* #findEachWhile provides a way to stop processing the query result early before
* all the beans have been read.
* </p>
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the QueryEachConsumer (SAM) interface which is better suited to use
* with Java8 closures.
* </p>
*
* <pre>{@code
*
* ebeanServer.find(Customer.class)
* .where().eq("status", Status.NEW)
* .order().asc("id")
* .findEach((Customer customer) -> {
*
* // do something with customer
* System.out.println("-- visit " + customer);
* });
*
* }</pre>
*
* @param consumer
* the consumer used to process the queried beans.
*/
public void findEach(QueryEachConsumer<T> consumer);
/**
* Execute the query using callbacks to a visitor to process the resulting
* beans one at a time.
* <p>
* Similar to findIterate() this query method does not require all the result
* beans to be all held in memory at once and as such is useful for processing
* large queries.
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the QueryEachWhileConsumer (SAM) interface which is better suited to use
* with Java8 closures.
* </p>
*
* <pre class="code">
*
* Query&lt;Customer&gt; query = server.find(Customer.class)
* .fetch(&quot;contacts&quot;, new FetchConfig().query(2))
* .where().gt(&quot;id&quot;, 0)
* .orderBy(&quot;id&quot;)
* .setMaxRows(2);
*
* query.findVisit(new QueryResultVisitor&lt;Customer&gt;() {
*
* public boolean accept(Customer customer) {
* // do something with customer
* System.out.println(&quot;-- visit &quot; + customer);
* return true;
* }
* });
* </pre>
*
* @param visitor
* the visitor used to process the queried beans.
*
* <pre>{@code
*
* ebeanServer.find(Customer.class)
* .fetch("contacts", new FetchConfig().query(2))
* .where().eq("status", Status.NEW)
* .order().asc("id")
* .setMaxRows(2000)
* .findEachWhile((Customer customer) -> {
*
* // do something with customer
* System.out.println("-- visit " + customer);
*
* // return true to continue processing or false to stop
* return (customer.getId() < 40);
* });
*
* }</pre>
*
* @param consumer
* the consumer used to process the queried beans.
*/
public void findVisit(QueryResultVisitor<T> visitor);
public void findEachWhile(QueryEachWhileConsumer<T> consumer);
/**
* Execute the query returning the list of objects.
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*
*
* <pre>{@code
*
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findList();
*
* }</pre>
*
* @see EbeanServer#findList(Query, Transaction)
*/
public List<T> findList();
@@ -483,7 +633,16 @@ public interface Query<T> extends Serializable {
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*
*
* <pre>{@code
*
* Set<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findSet();
*
* }</pre>
*
* @see EbeanServer#findSet(Query, Transaction)
*/
public Set<T> findSet();
@@ -498,11 +657,14 @@ public interface Query<T> extends Serializable {
* on the map. If one is not specified then the id property is used.
* </p>
*
* <pre class="code">
* Query&lt;Product&gt; query = Ebean.createQuery(Product.class);
* query.setMapKey(&quot;sku&quot;);
* Map&lt;?, Product&gt; map = query.findMap();
* </pre>
* <pre>{@code
*
* Map<?, Product> map =
* ebeanServer.find(Product.class)
* .setMapKey("sku")
* .findMap();
*
* }</pre>
*
* @see EbeanServer#findMap(Query, Transaction)
*/
@@ -525,32 +687,34 @@ public interface Query<T> extends Serializable {
* return 0 or 1 results.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // assuming the sku of products is unique...
* Product product =
* Ebean.find(Product.class)
* .where(&quot;sku = ?&quot;)
* .set(1, &quot;aa113&quot;)
* ebeanServer.find(Product.class)
* .where().eq("sku", "aa113")
* .findUnique();
* ...
* </pre>
* }</pre>
*
* <p>
* It is also useful with finding objects by their id when you want to specify
* further join information.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // Fetch order 1 and additionally fetch join its order details...
* Order order =
* Ebean.find(Order.class)
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch(&quot;details&quot;)
* .fetch("details")
* .findUnique();
*
* List&lt;OrderDetail&gt; details = order.getDetails();
*
* // the order details were eagerly loaded
* List<OrderDetail> details = order.getDetails();
* ...
* </pre>
* }</pre>
*/
public T findUnique();
@@ -589,21 +753,14 @@ public interface Query<T> extends Serializable {
/**
* Execute find list query in a background thread.
* <p>
* Deprecated with a view to simplifying internals.
* This query will execute in it's own PersistenceContext and using its own transaction.
* What that means is that it will not share any bean instances with other queries.
* </p>
*
*
* @return a Future object for the list result of the query
* @deprecated
*/
public FutureList<T> findFutureList();
/**
* This is being deprecated in favour of the simplier {@link Query#findPagedList(int, int)}.
*
* @deprecated
*/
public PagingList<T> findPagingList(int pageSize);
/**
* Return a PagedList for this query.
* <p>
@@ -616,7 +773,32 @@ public interface Query<T> extends Serializable {
* the query. This translates into SQL that uses limit offset, rownum or row_number function to
* limit the result set.
* </p>
*
*
* <h4>Example: typical use including total row count</h4>
* <pre>{@code
*
* // We want to find the first 100 new orders
* // ... 0 means first page
* // ... page size is 100
*
* PagedList<Order> pagedList
* = ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findPagedList(0, 100);
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* @param pageIndex
* The zero based index of the page.
* @param pageSize
@@ -626,19 +808,20 @@ public interface Query<T> extends Serializable {
public PagedList<T> findPagedList(int pageIndex, int pageSize);
/**
* Set a named bind parameter. Named parameters have a colon to prefix the
* name.
* Set a named bind parameter. Named parameters have a colon to prefix the name.
*
* <pre class="code">
* <pre>{@code
*
* // a query with a named parameter
* String oql = &quot;find order where status = :orderStatus&quot;;
* String oql = "find order where status = :orderStatus";
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, oql);
* Query<Order> query = ebeanServer.find(Order.class, oql);
*
* // bind the named parameter
* query.bind(&quot;orderStatus&quot;, OrderStatus.NEW);
* List&lt;Order&gt; list = query.findList();
* </pre>
* query.bind("orderStatus", OrderStatus.NEW);
* List<Order> list = query.findList();
*
* }</pre>
*
* @param name
* the parameter name
@@ -652,17 +835,19 @@ public interface Query<T> extends Serializable {
* position starts at 1 to be consistent with JDBC PreparedStatement. You need
* to set a parameter value for each ? you have in the query.
*
* <pre class="code">
* <pre>{@code
*
* // a query with a positioned parameter
* String oql = &quot;where status = ? order by id desc&quot;;
* String oql = "where status = ? order by id desc";
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, oql);
* Query<Order> query = ebeanServer.createQuery(Order.class, oql);
*
* // bind the parameter
* query.setParameter(1, OrderStatus.NEW);
*
* List&lt;Order&gt; list = query.findList();
* </pre>
* List<Order> list = query.findList();
*
* }</pre>
*
* @param position
* the parameter bind position starting from 1 (not 0)
@@ -678,12 +863,18 @@ public interface Query<T> extends Serializable {
* fetch joins.
* </p>
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* Order order = query.setId(1).join(&quot;details&quot;).findUnique();
* List&lt;OrderDetail&gt; details = order.getDetails();
* ...
* </pre>
* <pre>{@code
*
* Order order =
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch("details")
* .findUnique();
*
* // the order details were eagerly fetched
* List<OrderDetail> details = order.getDetails();
*
* }</pre>
*/
public Query<T> setId(Object id);
@@ -694,15 +885,17 @@ public interface Query<T> extends Serializable {
* {@link #setParameter(String, Object)}.
* </p>
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, &quot;top&quot;);
* <pre>{@code
*
* Query<Order> query = ebeanServer.createQuery(Order.class, "top");
* ...
* if (...) {
* query.where(&quot;status = :status and lower(customer.name) like :custName&quot;);
* query.setParameter(&quot;status&quot;, Order.NEW);
* query.setParameter(&quot;custName&quot;, &quot;rob%&quot;);
* query.where("status = :status and lower(customer.name) like :custName");
* query.setParameter("status", Order.NEW);
* query.setParameter("custName", "rob%");
* }
* </pre>
*
* }</pre>
*
* <p>
* Internally the addToWhereClause string is processed by removing named
@@ -722,13 +915,15 @@ public interface Query<T> extends Serializable {
/**
* Add a single Expression to the where clause returning the query.
*
* <pre class="code">
* List&lt;Order&gt; newOrders =
* Ebean.find(Order.class)
* .where().eq(&quot;status&quot;, Order.NEW)
* <pre>{@code
*
* List<Order> newOrders =
* ebeanServer.find(Order.class)
* .where().eq("status", Order.NEW)
* .findList();
* ...
* </pre>
*
* }</pre>
*/
public Query<T> where(Expression expression);
@@ -737,15 +932,16 @@ public interface Query<T> extends Serializable {
* ExpressionList. You can use this for adding multiple expressions to the
* where clause.
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, &quot;top&quot;);
* ...
* if (...) {
* query.where()
* .eq(&quot;status&quot;, Order.NEW)
* .ilike(&quot;customer.name&quot;,&quot;rob%&quot;);
* }
* </pre>
* <pre>{@code
*
* List<Order> orders =
* ebeanServer.find(Order.class)
* .where()
* .eq("status", Order.NEW)
* .ilike("customer.name","rob%")
* .findList();
*
* }</pre>
*
* @see Expr
* @return The ExpressionList for adding expressions to.
@@ -763,17 +959,18 @@ public interface Query<T> extends Serializable {
* week. In this case you can use filterMany() to filter the orders.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* List&lt;Customer&gt; list = Ebean
* .find(Customer.class)
* // .fetch(&quot;orders&quot;, new FetchConfig().lazy())
* // .fetch(&quot;orders&quot;, new FetchConfig().query())
* .fetch(&quot;orders&quot;).where().ilike(&quot;name&quot;, &quot;rob%&quot;).filterMany(&quot;orders&quot;)
* .eq(&quot;status&quot;, Order.Status.NEW).gt(
* &quot;orderDate&quot;, lastWeek).findList();
* List<Customer> list =
* ebeanServer.find(Customer.class)
* // .fetch("orders", new FetchConfig().lazy())
* // .fetch("orders", new FetchConfig().query())
* .fetch("orders")
* .where().ilike("name", "rob%")
* .filterMany("orders").eq("status", Order.Status.NEW).gt("orderDate", lastWeek)
* .findList();
*
* </pre>
* }</pre>
*
* <p>
* Please note you have to be careful that you add expressions to the correct
@@ -811,14 +1008,14 @@ public interface Query<T> extends Serializable {
* {@link #setParameter(String, Object)}.
* </p>
*
* <pre class="code">
* Query&lt;ReportOrder&gt; query = Ebean.createQuery(ReportOrder.class);
* ...
* if (...) {
* query.having(&quot;score &gt; :min&quot;);
* query.setParameter(&quot;min&quot;, 1);
* }
* </pre>
* <pre>{@code
*
* List<ReportOrder> query =
* ebeanServer.find(ReportOrder.class)
* .having("score > :min").setParameter("min", 1)
* .findList();
*
* }</pre>
*
* @param addToHavingClause
* the clause to append to the having clause which typically contains
@@ -950,17 +1147,17 @@ public interface Query<T> extends Serializable {
* If no property is set then the id property is used.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // Assuming sku is unique for products...
*
* Query&lt;Product&gt; query = Ebean.createQuery(Product.class);
*
* // use sku for keys...
* query.setMapKey(&quot;sku&quot;);
*
* Map&lt;?,Product&gt; productMap = query.findMap();
* ...
* </pre>
* Map<?,Product> productMap =
* ebeanServer.find(Product.class)
* // use sku for keys...
* .setMapKey("sku")
* .findMap();
*
* }</pre>
*
* @param mapKey
* the property to use as keys for a map.
@@ -1033,4 +1230,9 @@ public interface Query<T> extends Serializable {
* Return true if this query has forUpdate set.
*/
public boolean isForUpdate();
/**
* Set root table alias.
*/
public Query<T> alias(String alias);
}
@@ -0,0 +1,41 @@
package com.avaje.ebean;
/**
* Used to process a query result one bean at a time via a callback to this
* visitor.
* <p>
* If you wish to stop further processing return false from the accept method.
* </p>
* <p>
* Unlike findList() and findSet() using a QueryResultVisitor does not require
* all the beans in the query result to be held in memory at once. This makes
* QueryResultVisitor useful for processing large queries.
* </p>
*
* <pre>{@code
*
* Query<Customer> query = server.find(Customer.class)
* .where().eq("status", Status.NEW)
* .order().asc("id");
*
* query.findEach((Customer customer) -> {
*
* // do something with customer
* System.out.println("-- visit " + customer);
* });
*
* }</pre>
*
* @param <T>
* the type of entity bean being queried.
*/
public interface QueryEachConsumer<T> {
/**
* Process the bean.
*
* @param bean
* the entity bean to process
*/
public void accept(T bean);
}
@@ -0,0 +1,45 @@
package com.avaje.ebean;
/**
* Used to process a query result one bean at a time via a callback to this
* visitor.
* <p>
* If you wish to stop further processing return false from the accept method.
* </p>
* <p>
* Unlike findList() and findSet() using a QueryResultVisitor does not require
* all the beans in the query result to be held in memory at once. This makes
* QueryResultVisitor useful for processing large queries.
* </p>
* <p/>
* <pre class="code">
*
* Query&lt;Customer&gt; query = server.find(Customer.class)
* .fetch(&quot;contacts&quot;, new FetchConfig().query(2))
* .where().gt(&quot;id&quot;, 0)
* .orderBy(&quot;id&quot;)
* .setMaxRows(2);
*
* query.findEachWhile((Customer customer) -> {
*
* // do something with customer
* System.out.println(&quot;-- visit &quot; + customer);
*
* // return true to continue processing or false to stop
* return (customer.getId() < 40);
* });
* </pre>
*
* @param <T> the type of entity bean being queried.
*/
public interface QueryEachWhileConsumer<T> {
/**
* Process the bean and return true if you want to continue processing more
* beans. Return false if you want to stop processing further.
*
* @param bean the entity bean to process
* @return true to continue processing more beans or false to stop.
*/
public boolean accept(T bean);
}
+133 -62
View File
@@ -2,12 +2,7 @@ package com.avaje.ebean;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import com.avaje.ebean.util.CamelCaseHelper;
@@ -22,25 +17,25 @@ import com.avaje.ebean.util.CamelCaseHelper;
* <b>Unparsed RawSql:</b>
* </p>
* <p>
* When RawSql is created via RawSqlBuilder.unparsed(sql) then Ebean can not
* When RawSql is created via {@link RawSqlBuilder#unparsed(String)} then Ebean can not
* modify the SQL at all. It can't add any extra expressions into the SQL.
* </p>
* <p>
* <b>Parsed RawSql:</b>
* </p>
* <p>
* When RawSql is created via RawSqlBuilder.parse(sql) then Ebean will parse the
* When RawSql is created via {@link RawSqlBuilder#parse(String)} then Ebean will parse the
* SQL and find places in the SQL where it can add extra where expressions, add
* extra having expressions or replace the order by clause. If you want to
* explicitly tell Ebean where these insertion points are you can place special
* strings into your SQL (${where} or ${andWhere} and ${having} or
* ${andHaving}).
* strings into your SQL ({@code ${where}} or {@code ${andWhere}} and {@code ${having}} or
* {@code ${andHaving})}.
* </p>
* <p>
* If the SQL already includes a WHERE clause put in ${andWhere} in the location
* If the SQL already includes a WHERE clause put in {@code ${andWhere}} in the location
* you want Ebean to add any extra where expressions. If the SQL doesn't have a
* WHERE clause put ${where} in instead. Similarly you can put in ${having} or
* ${andHaving} where you want Ebean put add extra having expressions.
* WHERE clause put {@code ${where}} in instead. Similarly you can put in {@code ${having}} or
* {@code ${andHaving}} where you want Ebean put add extra having expressions.
* </p>
* <p>
* <b>Aggregates:</b>
@@ -56,23 +51,19 @@ import com.avaje.ebean.util.CamelCaseHelper;
* to hold the values for the aggregate functions (sum etc) and a &#064;OneToOne
* to Order.
* </p>
* <p>
* &nbsp;
* </p>
* <p>
* <b>Example OrderAggregate</b>
* </p>
*
* <h3>Example OrderAggregate</h3>
*
* <pre class="code">
* <pre>{@code
* ...
* // &#064;Sql indicates to that this bean
* // @Sql indicates to that this bean
* // is based on RawSql rather than a table
*
* &#064;Entity
* &#064;Sql
* @Entity
* @Sql
* public class OrderAggregate {
*
* &#064;OneToOne
* @OneToOne
* Order order;
*
* Double totalAmount;
@@ -81,35 +72,38 @@ import com.avaje.ebean.util.CamelCaseHelper;
*
* // getters and setters
* ...
* </pre>
* <p>
* <b>Example 1:</b>
* </p>
*
* }</pre>
*
* <h3>Example 1:</h3>
*
* <pre class="code">
* String sql = &quot; select order_id, o.status, c.id, c.name, sum(d.order_qty*d.unit_price) as totalAmount&quot;
* + &quot; from o_order o&quot;
* + &quot; join o_customer c on c.id = o.kcustomer_id &quot;
* + &quot; join o_order_detail d on d.order_id = o.id &quot; + &quot; group by order_id, o.status &quot;;
* <pre>{@code
*
* String sql = " select order_id, o.status, c.id, c.name, sum(d.order_qty*d.unit_price) as totalAmount"
* + " from o_order o"
* + " join o_customer c on c.id = o.kcustomer_id "
* + " join o_order_detail d on d.order_id = o.id " + " group by order_id, o.status ";
*
* RawSql rawSql = RawSqlBuilder.parse(sql)
* RawSql rawSql = RawSqlBuilder.parse(sql)
* // map the sql result columns to bean properties
* .columnMapping(&quot;order_id&quot;, &quot;order.id&quot;).columnMapping(&quot;o.status&quot;, &quot;order.status&quot;)
* .columnMapping(&quot;c.id&quot;, &quot;order.customer.id&quot;)
* .columnMapping(&quot;c.name&quot;, &quot;order.customer.name&quot;)
* .columnMapping("order_id", "order.id")
* .columnMapping("o.status", "order.status")
* .columnMapping("c.id", "order.customer.id")
* .columnMapping("c.name", "order.customer.name")
* // we don't need to map this one due to the sql column alias
* // .columnMapping(&quot;sum(d.order_qty*d.unit_price)&quot;, &quot;totalAmount&quot;)
* // .columnMapping("sum(d.order_qty*d.unit_price)", "totalAmount")
* .create();
*
* Query&lt;OrderAggregate&gt; query = Ebean.find(OrderAggregate.class);
* query.setRawSql(rawSql).where().gt(&quot;order.id&quot;, 0).having().gt(&quot;totalAmount&quot;, 20);
* List<OrderAggregate> list = Ebean.find(OrderAggregate.class)
* .setRawSql(rawSql)
* .where().gt("order.id", 0)
* .having().gt("totalAmount", 20)
* .findList();
*
* List&lt;OrderAggregate&gt; list = query.findList();
* </pre>
*
* }</pre>
*
* <p>
* <b>Example 2:</b>
* </p>
* <h3>Example 2:</h3>
*
* <p>
* The following example uses a FetchConfig().query() so that after the initial
@@ -117,21 +111,59 @@ import com.avaje.ebean.util.CamelCaseHelper;
* associated order status, orderDate along with the customer name.
* </p>
*
* <pre class="code">
* String sql = &quot; select order_id, 'ignoreMe', sum(d.order_qty*d.unit_price) as totalAmount &quot;
* + &quot; from o_order_detail d&quot;
* + &quot; group by order_id &quot;;
* <pre>{@code
*
* String sql = " select order_id, 'ignoreMe', sum(d.order_qty*d.unit_price) as totalAmount "
* + " from o_order_detail d"
* + " group by order_id ";
*
* RawSql rawSql = RawSqlBuilder.parse(sql).columnMapping(&quot;order_id&quot;, &quot;order.id&quot;)
* .columnMappingIgnore(&quot;'ignoreMe'&quot;).create();
* RawSql rawSql = RawSqlBuilder.parse(sql)
* .columnMapping("order_id", "order.id")
* .columnMappingIgnore("'ignoreMe'")
* .create();
*
* Query&lt;OrderAggregate&gt; query = Ebean.find(OrderAggregate.class);
* query.setRawSql(rawSql).fetch(&quot;order&quot;, &quot;status,orderDate&quot;, new FetchConfig().query())
* .fetch(&quot;order.customer&quot;, &quot;name&quot;).where()
* .gt(&quot;order.id&quot;, 0).having().gt(&quot;totalAmount&quot;, 20).order().desc(&quot;totalAmount&quot;).setMaxRows(10);
*
* </pre>
* List<OrderAggregate> orders = Ebean.find(OrderAggregate.class)
* .setRawSql(rawSql)
* .fetch("order", "status,orderDate", new FetchConfig().query())
* .fetch("order.customer", "name")
* .where().gt("order.id", 0)
* .having().gt("totalAmount", 20)
* .order().desc("totalAmount")
* .setMaxRows(10)
* .findList();
*
* }</pre>
*
*
* <h3>Example 3: tableAliasMapping</h3>
* <p>
* Instead of mapping each column you can map each table alias to a path using tableAliasMapping().
* </p>
* <pre>{@code
*
* String rs = "select o.id, o.status, c.id, c.name, "+
* " d.id, d.order_qty, p.id, p.name " +
* "from o_order o join o_customer c on c.id = o.kcustomer_id " +
* "join o_order_detail d on d.order_id = o.id " +
* "join o_product p on p.id = d.product_id " +
* "where o.id <= :maxOrderId and p.id = :productId "+
* "order by o.id, d.id asc";
*
* RawSql rawSql = RawSqlBuilder.parse(rs)
* .tableAliasMapping("c", "customer")
* .tableAliasMapping("d", "details")
* .tableAliasMapping("p", "details.product")
* .create();
*
* List<Order> ordersFromRaw = Ebean.find(Order.class)
* .setRawSql(rawSql)
* .setParameter("maxOrderId", 2)
* .setParameter("productId", 1)
* .findList();
*
* }</pre>
*
*
* <p>
* Note that lazy loading also works with object graphs built with RawSql.
* </p>
@@ -222,6 +254,8 @@ public final class RawSql implements Serializable {
private final boolean andHavingExpr;
private final String orderByPrefix;
private final String orderBy;
private final boolean distinct;
@@ -240,6 +274,7 @@ public final class RawSql implements Serializable {
this.preWhere = null;
this.andHavingExpr = false;
this.andWhereExpr = false;
this.orderByPrefix = null;
this.orderBy = null;
this.distinct = false;
}
@@ -248,8 +283,7 @@ public final class RawSql implements Serializable {
* Construct for parsed SQL.
*/
protected Sql(int queryHashCode, String preFrom, String preWhere, boolean andWhereExpr,
String preHaving, boolean andHavingExpr,
String orderBy, boolean distinct) {
String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) {
this.queryHashCode = queryHashCode;
this.parsed = true;
@@ -259,6 +293,7 @@ public final class RawSql implements Serializable {
this.preWhere = preWhere;
this.andHavingExpr = andHavingExpr;
this.andWhereExpr = andWhereExpr;
this.orderByPrefix = orderByPrefix;
this.orderBy = orderBy;
this.distinct = distinct;
}
@@ -337,6 +372,14 @@ public final class RawSql implements Serializable {
return andHavingExpr;
}
/**
* Return the 'order by' keywords.
* This can contain additional keywords, for example 'order siblings by' as Oracle syntax.
*/
public String getOrderByPrefix() {
return (orderByPrefix == null) ? "order by" : orderByPrefix;
}
/**
* Return the SQL ORDER BY clause.
*/
@@ -429,9 +472,8 @@ public final class RawSql implements Serializable {
for (Column c : dbColumnMap.values()) {
pMap.put(c.getPropertyName(), c.getDbColumn());
pcMap.put(c.getPropertyName(), c);
hc = 31 * hc + c.getPropertyName() == null ? 0 : c.getPropertyName().hashCode();
hc = 31 * hc + c.getDbColumn() == null ? 0 : c.getDbColumn().hashCode();
hc = 31 * hc + ((c.getPropertyName() == null) ? 0 : c.getPropertyName().hashCode());
hc = 31 * hc + ((c.getDbColumn() == null) ? 0 : c.getDbColumn().hashCode());
}
this.propertyMap = Collections.unmodifiableMap(pMap);
this.propertyColumnMap = Collections.unmodifiableMap(pcMap);
@@ -531,6 +573,22 @@ public final class RawSql implements Serializable {
return dbColumnMap.values().iterator();
}
/**
* Modify any column mappings with the given table alias to have the path prefix.
* <p>
* For example modify all mappings with table alias "c" to have the path prefix "customer".
* </p>
*/
public void tableAliasMapping(String tableAlias, String path) {
String startMatch = tableAlias+".";
for (Map.Entry<String, Column> entry : dbColumnMap.entrySet()) {
if (entry.getKey().startsWith(startMatch)) {
entry.getValue().tableAliasMapping(path);
}
}
}
/**
* A Column of the RawSql that is mapped to a bean property (or ignored).
*/
@@ -612,10 +670,23 @@ public final class RawSql implements Serializable {
return propertyName;
}
/**
* Set the property name mapped to this db column.
*/
private void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
/**
* Prepend the path to the property name.
* <p/>
* For example if path is "customer" then "name" becomes "customer.name".
*/
public void tableAliasMapping(String path) {
if (path != null) {
propertyName = path + "." + propertyName;
}
}
}
}
}
@@ -70,13 +70,6 @@ public class RawSqlBuilder {
ColumnMapping mapping = DRawSqlColumnsParser.parse(select);
return new RawSqlBuilder(sql2, mapping);
}
private RawSqlBuilder(ResultSet resultSet, ColumnMapping columnMapping) {
this.resultSet = resultSet;
this.columnMapping = columnMapping;
this.sql = null;
}
private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) {
this.sql = sql;
@@ -108,6 +101,17 @@ public class RawSqlBuilder {
return columnMapping(dbColumn, IGNORE_COLUMN);
}
/**
* Modify any column mappings with the given table alias to have the path prefix.
* <p>
* For example modify all mappings with table alias "c" to have the path prefix "customer".
* </p>
*/
public RawSqlBuilder tableAliasMapping(String tableAlias, String path) {
columnMapping.tableAliasMapping(tableAlias, path);
return this;
}
/**
* Create the immutable RawSql object. Do this after all the column mapping
* has been defined.
@@ -122,6 +126,7 @@ public class RawSqlBuilder {
protected Sql getSql() {
return sql;
}
}
@@ -1,6 +1,6 @@
package com.avaje.ebean;
public class SimpleTextParser {
class SimpleTextParser {
private final String oql;
private final char[] chars;
@@ -10,9 +10,7 @@ public class SimpleTextParser {
private String word;
private String lowerWord;
private int openParenthesisCount;
public SimpleTextParser(String oql) {
SimpleTextParser(String oql) {
this.oql = oql;
this.chars = oql.toCharArray();
this.eof = oql.length();
@@ -22,10 +20,6 @@ public class SimpleTextParser {
return pos;
}
public String getOql() {
return oql;
}
public String getWord() {
return word;
}
@@ -104,7 +98,7 @@ public class SimpleTextParser {
private void moveToClose() {
pos++;
openParenthesisCount = 0;
int openParenthesisCount = 0;
for (; pos < eof; pos++) {
char c = chars[pos];
@@ -143,11 +137,7 @@ public class SimpleTextParser {
if (isOperator(c)) {
return !isOperator;
}
if (c == '(') {
return true;
}
return isOperator;
return c == '(' || isOperator;
}
private boolean isOperator(char c) {
@@ -91,6 +91,11 @@ public interface SqlUpdate {
*/
public String getSql();
/**
* Return the generated sql that has named parameters converted to positioned parameters.
*/
public String getGeneratedSql();
/**
* Return the timeout used to execute this statement.
*/
+69 -25
View File
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.config.PersistBatch;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
@@ -35,6 +37,11 @@ public interface Transaction extends Closeable {
*/
public static final int SERIALIZABLE = java.sql.Connection.TRANSACTION_SERIALIZABLE;
/**
* Register a TransactionCallback with this transaction.
*/
public void register(TransactionCallback callback);
/**
* Return true if this transaction is read only.
*/
@@ -123,47 +130,93 @@ public interface Transaction extends Closeable {
* Example: batch processing executing every 3 rows
* </p>
*
* <pre class="code">
* String data = &quot;This is a simple test of the batch processing&quot;
* + &quot; mode and the transaction execute batch method&quot;;
* <pre>{@code
*
* String data = "This is a simple test of the batch processing"
* + " mode and the transaction execute batch method";
*
* String[] da = data.split(&quot; &quot;);
* String[] da = data.split(" ");
*
* String sql = &quot;{call sp_t3(?,?)}&quot;;
* String sql = "{call sp_t3(?,?)}";
*
* CallableSql cs = new CallableSql(sql);
* cs.registerOut(2, Types.INTEGER);
*
* // (optional) inform eBean this stored procedure
* // inserts into a table called sp_test
* cs.addModification(&quot;sp_test&quot;, true, false, false);
* cs.addModification("sp_test", true, false, false);
*
* Transaction t = Ebean.beginTransaction();
* t.setBatchMode(true);
* t.setBatchSize(3);
* Transaction txn = ebeanServer.beginTransaction();
* txn.setBatchMode(true);
* txn.setBatchSize(3);
* try {
* for (int i = 0; i &lt; da.length;) {
*
* for (int i = 0; i < da.length;) {
* cs.setParameter(1, da[i]);
* Ebean.execute(cs);
* ebeanServer.execute(cs);
* }
*
* // NB: commit implicitly flushes
* Ebean.commitTransaction();
* txn.commit();
*
* } finally {
* Ebean.endTransaction();
* txn.end();
* }
* </pre>
*
* }</pre>
*
*/
public void setBatchMode(boolean useBatch);
/**
* The JDBC batch mode to use for this transaction.
* <p>
* If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
* and this would be useful if the request cascades to detail beans.
* </p>
*
* @param persistBatchMode the batch mode to use for this transaction
*
* @see com.avaje.ebean.config.ServerConfig#setPersistBatch(com.avaje.ebean.config.PersistBatch)
*/
public void setBatch(PersistBatch persistBatchMode);
/**
* Return the batch mode at the transaction level.
*/
public PersistBatch getBatch();
/**
* Set the JDBC batch mode to use for a save() or delete() request.
* <p>
* This only takes effect when batch mode on the transaction has not already meant that
* JDBC batch mode is being used.
* </p>
* <p>
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
* </p>
*
* @param batchOnCascadeMode the batch mode to use per save(), insert(), update() or delete()
*
* @see com.avaje.ebean.config.ServerConfig#setPersistBatchOnCascade(com.avaje.ebean.config.PersistBatch)
*/
public void setBatchOnCascade(PersistBatch batchOnCascadeMode);
/**
* Return the batch mode at the request level (for each save(), insert(), update() or delete()).
*/
public PersistBatch getBatchOnCascade();
/**
* Specify the number of statements before a batch is flushed automatically.
*/
public void setBatchSize(int batchSize);
/**
* Return the current batch size.
*/
public int getBatchSize();
/**
* Specify if you want batched inserts to use getGeneratedKeys.
* <p>
@@ -223,20 +276,11 @@ public interface Transaction extends Closeable {
* <li>the batch size is reached</li>
* <li>A query is executed on the same transaction</li>
* <li>UpdateSql or CallableSql are mixed with bean save and delete</li>
* <li>Transaction commit occurs</li>
* </ul>
*/
public void flushBatch() throws PersistenceException, OptimisticLockException;
/**
* Deprecated in favour of {@link #flushBatch()}.
* <p>
* Exactly the same as flushBatch. Deprecated as a name change.
* </p>
*
* @deprecated Please use flushBatch
*/
public void batchFlush() throws PersistenceException, OptimisticLockException;
/**
* Return the underlying Connection object.
* <p>
@@ -0,0 +1,34 @@
package com.avaje.ebean;
/**
* Provides a callback that can be registered with a Transaction.
* <p/>
* The callback methods are called just prior to and after the transaction performs a commit or rollback.
* <p/>
* A typical use of TransactionCallback would be to clean up non-transactional resources like files. For example,
* when processing files on postCommit/postRollback clean up the associated files. As another example when
* on postCommit of a delete remove associated resources from the file system or remote service.
*/
public interface TransactionCallback {
/**
* Perform processing just prior to the transaction commit.
*/
void preCommit();
/**
* Perform processing just after the transaction commit.
*/
void postCommit();
/**
* Perform processing just prior to the transaction rollback.
*/
void preRollback();
/**
* Perform processing just after the transaction rollback.
*/
void postRollback();
}
@@ -0,0 +1,42 @@
package com.avaje.ebean;
/**
* Adapter that can be extended for easier implementation of TransactionCallback.
* <p/>
* Provides 'no operation' implementation for each of the TransactionCallback methods. It is expected that this
* class is extended and override the methods you need to.
*/
public abstract class TransactionCallbackAdapter implements TransactionCallback {
/**
* Perform processing just prior to the transaction commit.
*/
@Override
public void preCommit() {
// do nothing - override as necessary
}
/**
* Perform processing just after the transaction commit.
*/
@Override
public void postCommit() {
// do nothing - override as necessary
}
/**
* Perform processing just prior to the transaction rollback.
*/
@Override
public void preRollback() {
// do nothing - override as necessary
}
/**
* Perform processing just after the transaction rollback.
*/
@Override
public void postRollback() {
// do nothing - override as necessary
}
}
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.config.PersistBatch;
import java.util.ArrayList;
/**
@@ -28,12 +30,39 @@ public final class TxScope {
TxIsolation isolation;
PersistBatch batch;
PersistBatch batchOnCascade;
int batchSize;
boolean readOnly;
ArrayList<Class<? extends Throwable>> rollbackFor;
ArrayList<Class<? extends Throwable>> noRollbackFor;
/**
* Return true if PersistBatch has been set.
*/
public boolean isBatchSet() {
return batch != null && batch != PersistBatch.INHERIT;
}
/**
* Return true if batch on cascade has been set.
*/
public boolean isBatchOnCascadeSet() {
return batchOnCascade != null && batchOnCascade != PersistBatch.INHERIT;
}
/**
* Return true if batch size has been set.
*/
public boolean isBatchSizeSet() {
return batchSize > 0;
}
/**
* Helper method to create a TxScope with REQUIRES.
*/
@@ -114,6 +143,51 @@ public final class TxScope {
return this;
}
/**
* Return the batch mode.
*/
public PersistBatch getBatch() {
return batch;
}
/**
* Set the batch mode to use.
*/
public TxScope setBatch(PersistBatch batch) {
this.batch = batch;
return this;
}
/**
* Return the batch on cascade mode.
*/
public PersistBatch getBatchOnCascade() {
return batchOnCascade;
}
/**
* Set the batch on cascade mode.
*/
public TxScope setBatchOnCascade(PersistBatch batchOnCascade) {
this.batchOnCascade = batchOnCascade;
return this;
}
/**
* Return the batch size. 0 means use the default value.
*/
public int getBatchSize() {
return batchSize;
}
/**
* Set the batch size to use.
*/
public TxScope setBatchSize(int batchSize) {
this.batchSize = batchSize;
return this;
}
/**
* Return if the transaction should be treated as read only.
*/
@@ -20,7 +20,7 @@ import com.avaje.ebean.Query;
* You may also put use the Transient annotation with the Formula annotation.
* The effect of the Transient annotation in this case is that the formula will
* <b>NOT</b> be included in queries by default - you have to explicitly include
* it via {@link Query#select(String)} or {@link Query#join(String, String)}.
* it via {@link Query#select(String)} or {@link Query#fetch(String, String, com.avaje.ebean.FetchConfig)}.
* You may want to do this if the Formula is relatively expensive and only want
* it included in the query when you explicitly state it.
* </p>
@@ -1,103 +1,128 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.avaje.ebean.TxIsolation;
import com.avaje.ebean.TxType;
/**
* Specify transaction scoping for a method.
* <p>
* <b><i> This is only supported if "Enhancement" is used via javaagent, ANT
* task or IDE enhancement plugin etc. </i></b>
* </p>
* <p>
* Note: Currently there are 3 known annotations that perform this role.
* <ul>
* <li>EJB's javax.ejb.TransactionAttribute</li>
* <li>Spring's org.springframework.transaction.annotation.Transactional</li>
* <li>and this one, Ebean's own com.avaje.ebean.annotation.Transactional</li>
* </ul>
* Spring created their one because the EJB annotation does not support features
* such as isolation level and specifying rollbackOn, noRollbackOn exceptions.
* This one exists for Ebean because I agree that the standard one is
* insufficient and don't want to include a dependency on Spring.
* </p>
* <p>
* The default behaviour of EJB (and hence Spring) is to NOT ROLLBACK on checked
* exceptions. I find this very counter-intuitive. Ebean will provide a property
* to set the default behaviour to rollback on any exception and optionally
* change the setting to be consistent with EJB/Spring if people wish to do so.
* </p>
*
* <pre class="code">
*
* // a normal class
* public class MySimpleUserService {
*
* // this method is transactional automatically handling
* // transaction begin, commit and rollback etc
* &#064;Transactional
* public void runInTrans() throws IOException {
*
* // tasks performed within the transaction
* ...
* // find some objects
* Customer cust = Ebean.find(Customer.class, 1);
*
* Order order = ...;
* ...
* // save some objects
* Ebean.save(customer);
* Ebean.save(order);
* }
* </pre>
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
/**
* The type of transaction scoping. Defaults to REQUIRED.
*/
TxType type() default TxType.REQUIRED;
/**
* The transaction isolation level this transaction should have.
* <p>
* This will only be used if this scope creates the transaction. If the
* transaction has already started then this will currently be ignored (you
* could argue that it should throw an exception).
* </p>
*/
TxIsolation isolation() default TxIsolation.DEFAULT;
/**
* Set this to true if the transaction should be only contain queries.
*/
boolean readOnly() default false;
/**
* The name of the server that you want the transaction to be created from.
* <p>
* If left blank the 'default' server is used.
* </p>
*/
String serverName() default "";
// int timeout() default 0;
/**
* The throwable's that will explicitly cause a rollback to occur.
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* The throwable's that will explicitly NOT cause a rollback to occur.
*/
Class<? extends Throwable>[] noRollbackFor() default {};
};
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.avaje.ebean.TxIsolation;
import com.avaje.ebean.TxType;
import com.avaje.ebean.config.PersistBatch;
/**
* Specify transaction scoping for a method.
* <p>
* <b><i> This is only supported if "Enhancement" is used via javaagent, ANT
* task or IDE enhancement plugin etc. </i></b>
* </p>
* <p>
* Note: Currently there are 3 known annotations that perform this role.
* <ul>
* <li>EJB's javax.ejb.TransactionAttribute</li>
* <li>Spring's org.springframework.transaction.annotation.Transactional</li>
* <li>and this one, Ebean's own com.avaje.ebean.annotation.Transactional</li>
* </ul>
* Spring created their one because the EJB annotation does not support features
* such as isolation level and specifying rollbackOn, noRollbackOn exceptions.
* This one exists for Ebean because I agree that the standard one is
* insufficient and don't want to include a dependency on Spring.
* </p>
* <p>
* The default behaviour of EJB (and hence Spring) is to NOT ROLLBACK on checked
* exceptions. I find this very counter-intuitive. Ebean will provide a property
* to set the default behaviour to rollback on any exception and optionally
* change the setting to be consistent with EJB/Spring if people wish to do so.
* </p>
*
* <pre>{@code
*
* // a normal class
* public class MySimpleUserService {
*
* // this method is transactional automatically handling
* // transaction begin, commit and rollback etc
* @Transactional
* public void runInTrans() throws IOException {
*
* // tasks performed within the transaction
* ...
* // find some objects
* Customer cust = ebeanServer.find(Customer.class, 42);
*
* Order order = ...;
* ...
* // save some objects
* ebeanServer.save(customer);
* ebeanServer.save(order);
* }
*
* }</pre>
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
/**
* The type of transaction scoping. Defaults to REQUIRED.
*/
TxType type() default TxType.REQUIRED;
/**
* Persist batch mode for the transaction.
*/
PersistBatch batch() default PersistBatch.INHERIT;
/**
* Persist batch mode for the request if not set on the transaction.
* <p>
* If batch is set to NONE then batchOnCascade can be set to INSERT or ALL
* and then each save(), delete(), insert(), update() request that cascades
* to child beans can use JDBC batch.
* </p>
*/
PersistBatch batchOnCascade() default PersistBatch.INHERIT;
/**
* The batch size to use when using JDBC batch mode.
* <p>
* If unset this defaults to the value set in ServerConfig.
* </p>
*/
int batchSize() default 0;
/**
* The transaction isolation level this transaction should have.
* <p>
* This will only be used if this scope creates the transaction. If the
* transaction has already started then this will currently be ignored (you
* could argue that it should throw an exception).
* </p>
*/
TxIsolation isolation() default TxIsolation.DEFAULT;
/**
* Set this to true if the transaction should be only contain queries.
*/
boolean readOnly() default false;
/**
* The name of the server that you want the transaction to be created from.
* <p>
* If left blank the 'default' server is used.
* </p>
*/
String serverName() default "";
// int timeout() default 0;
/**
* The Throwable's that will explicitly cause a rollback to occur.
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* The Throwable's that will explicitly NOT cause a rollback to occur.
*/
Class<? extends Throwable>[] noRollbackFor() default {};
};
@@ -31,6 +31,13 @@ public interface BeanCollection<E> extends Serializable {
ALL
}
/**
* Reset the collection back to an empty state ready for reloading.
* <p>
* This is done as part of bean refresh.
*/
public void reset(EntityBean ownerBean, String propertyName);
/**
* Return true if the collection is empty and untouched. Used to detect if a
* collection was 'cleared' deliberately or just un-initialised.
@@ -47,14 +54,6 @@ public interface BeanCollection<E> extends Serializable {
*/
public String getPropertyName();
/**
* Return the index position of this collection in the lazy/query loader.
* <p>
* Used for batch loading of collections.
* </p>
*/
public int getLoaderIndex();
/**
* Check after the lazy load that the underlying collection is not null
* (handle case where join to many not outer).
@@ -84,9 +83,17 @@ public interface BeanCollection<E> extends Serializable {
public void setBeanCollectionTouched(BeanCollectionTouched notify);
/**
* Set the loader that will be used to lazy/query load this collection.
* Return true if the collection has been registered with the batch loading context.
*/
public void setLoader(int beanLoaderIndex, BeanCollectionLoader beanLoader);
public boolean isRegisteredWithLoadContext();
/**
* Set the loader that will be used to lazy/query load this collection.
* <p>
* This is effectively the batch loading context this collection is registered with.
* </p>
*/
public void setLoader(BeanCollectionLoader beanLoader);
/**
* Set to true if you want the BeanCollection to be treated as read only. This
@@ -108,11 +115,6 @@ public interface BeanCollection<E> extends Serializable {
*/
public void internalAdd(Object bean);
/**
* Returns the underlying List Set or Map object.
*/
public Object getActualCollection();
/**
* Return the number of elements in the List Set or Map.
*/
@@ -47,9 +47,6 @@ public final class EntityBeanIntercept implements Serializable {
*/
private EntityBean owner;
/**
* The parent bean by relationship (1-1 or 1-M).
*/
private EntityBean embeddedOwner;
private int embeddedOwnerIndex;
@@ -175,7 +172,7 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* Return the parent bean (by relationship).
* Return the owning bean for an embedded bean.
*/
public Object getEmbeddedOwner() {
return embeddedOwner;
@@ -189,8 +186,7 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* Special case for a OneToOne, Set the parent bean (by relationship). This is
* the owner of a 1-1.
* Set the embedded beans owning bean.
*/
public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) {
this.embeddedOwner = parentBean;
@@ -29,7 +29,10 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
protected transient ExpressionList<?> filterMany;
protected int loaderIndex;
/**
* Flag set when registered with the batch loading context.
*/
protected boolean registeredWithLoadContext;
protected String ebeanServerName;
@@ -38,12 +41,12 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
/**
* The owning bean (used for lazy fetch).
*/
protected final EntityBean ownerBean;
protected EntityBean ownerBean;
/**
* The name of this property in the owning bean (used for lazy fetch).
*/
protected final String propertyName;
protected String propertyName;
protected ModifyHolder<E> modifyHolder;
@@ -62,8 +65,6 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
* Constructor not non-lazy loading collection.
*/
public AbstractBeanCollection() {
this.ownerBean = null;
this.propertyName = null;
}
/**
@@ -85,10 +86,6 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
return propertyName;
}
public int getLoaderIndex() {
return loaderIndex;
}
public ExpressionList<?> getFilterMany() {
return filterMany;
}
@@ -131,8 +128,12 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
this.beanCollectionTouched = notify;
}
public void setLoader(int beanLoaderIndex, BeanCollectionLoader loader) {
this.loaderIndex = beanLoaderIndex;
public boolean isRegisteredWithLoadContext() {
return registeredWithLoadContext;
}
public void setLoader(BeanCollectionLoader loader) {
this.registeredWithLoadContext = true;
this.loader = loader;
this.ebeanServerName = loader.getName();
}
@@ -46,6 +46,14 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
super(loader, ownerBean, propertyName);
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.list = null;
this.touched = false;
}
@Override
public boolean isEmptyAndUntouched() {
return !touched && (list == null || list.isEmpty());
@@ -130,13 +138,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
return list;
}
/**
* Returns the underlying list.
*/
public Object getActualCollection() {
return list;
}
/**
* Return true if the underlying list is populated.
*/
@@ -39,7 +39,15 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
public BeanMap(BeanCollectionLoader ebeanServer, EntityBean ownerBean, String propertyName) {
super(ebeanServer, ownerBean, propertyName);
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.map = null;
this.touched = false;
}
public boolean isEmptyAndUntouched() {
return !touched && (map == null || map.isEmpty());
}
@@ -146,15 +154,8 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
}
/**
* Returns the underlying map.
*/
public Object getActualCollection() {
return map;
}
public String toString() {
StringBuffer sb = new StringBuffer(50);
StringBuilder sb = new StringBuilder(50);
sb.append("BeanMap ");
if (isReadOnly()) {
sb.append("readOnly ");
@@ -40,6 +40,14 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
super(loader, ownerBean, propertyName);
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.set = null;
this.touched = false;
}
public boolean isEmptyAndUntouched() {
return !touched && (set == null || set.isEmpty());
}
@@ -9,7 +9,7 @@ import org.slf4j.LoggerFactory;
/**
* Provides some base implementation for NamingConventions.
*
*
* @author emcgreal
*/
public abstract class AbstractNamingConvention implements NamingConvention {
@@ -41,7 +41,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
/** Used to trim off extra prefix for M2M. */
protected int rhsPrefixLength = 3;
protected boolean useForeignKeyPrefix = true;
protected boolean useForeignKeyPrefix;
/**
* Construct with a sequence format and useForeignKeyPrefix setting.
@@ -53,12 +53,12 @@ public abstract class AbstractNamingConvention implements NamingConvention {
/**
* Construct with a sequence format.
*
* @param sequenceFormat
* the sequence format
*
* @param sequenceFormat the sequence format
*/
public AbstractNamingConvention(String sequenceFormat) {
this.sequenceFormat = sequenceFormat;
this.useForeignKeyPrefix = true;
}
/**
@@ -125,10 +125,9 @@ public abstract class AbstractNamingConvention implements NamingConvention {
* The format should include "{table}". When generating the sequence name
* {table} is replaced with the actual table name.
* </p>
*
* @param sequenceFormat
* string containing "{table}" which is replaced with the actual
* table name to generate the sequence name.
*
* @param sequenceFormat string containing "{table}" which is replaced with the actual
* table name to generate the sequence name.
*/
public void setSequenceFormat(String sequenceFormat) {
this.sequenceFormat = sequenceFormat;
@@ -173,7 +172,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
* This first checks for the @Table annotation and if not present uses the
* naming convention to define the table name.
* </p>
*
*
* @see #getTableNameFromAnnotation(Class)
* @see #getTableNameByConvention(Class)
*/
@@ -240,8 +239,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
if (t != null && !isEmpty(t.name())) {
// Note: empty catalog and schema are converted to null
// Only need to convert quoted identifiers from annotations
return new TableName(quoteIdentifiers(t.catalog()), quoteIdentifiers(t.schema()),
quoteIdentifiers(t.name()));
return new TableName(quoteIdentifiers(t.catalog()), quoteIdentifiers(t.schema()), quoteIdentifiers(t.name()));
}
// No annotation
@@ -279,4 +277,16 @@ public abstract class AbstractNamingConvention implements NamingConvention {
}
return false;
}
/**
* Load settings from properties.
*/
@Override
public void loadFromProperties(PropertiesWrapper properties) {
useForeignKeyPrefix = properties.getBoolean("namingConvention.useForeignKeyPrefix", useForeignKeyPrefix);
sequenceFormat = properties.get("namingConvention.sequenceFormat", sequenceFormat);
schema = properties.get("namingConvention.schema", schema);
}
}
@@ -155,14 +155,6 @@ public class AutofetchConfig {
return logDirectory;
}
/**
* Return the log directory substituting any expressions such as
* ${catalina.base} etc.
*/
public String getLogDirectoryWithEval() {
return GlobalProperties.evaluateExpressions(logDirectory);
}
/**
* Set the directory to put the autofetch log in.
*/
@@ -234,22 +226,20 @@ public class AutofetchConfig {
/**
* Load the settings from the properties file.
*/
public void loadSettings(GlobalProperties.PropertySource p) {
public void loadSettings(PropertiesWrapper p) {
logDirectory = p.get("autofetch.logDirectory", null);
queryTuning = p.getBoolean("autofetch.querytuning", false);
queryTuningAddVersion = p.getBoolean("autofetch.queryTuningAddVersion", false);
garbageCollectionOnShutdown = p.getBoolean("autofetch.garbageCollectionOnShutdown", false);
logDirectory = p.get("autofetch.logDirectory", logDirectory);
queryTuning = p.getBoolean("autofetch.querytuning", queryTuning);
queryTuningAddVersion = p.getBoolean("autofetch.queryTuningAddVersion", queryTuningAddVersion);
garbageCollectionOnShutdown = p.getBoolean("autofetch.garbageCollectionOnShutdown", garbageCollectionOnShutdown);
profiling = p.getBoolean("autofetch.profiling", false);
mode = p.getEnum(AutofetchMode.class, "autofetch.implicitmode", AutofetchMode.DEFAULT_ONIFEMPTY);
profiling = p.getBoolean("autofetch.profiling", profiling);
mode = p.getEnum(AutofetchMode.class, "autofetch.implicitmode", mode);
profilingMin = p.getInt("autofetch.profiling.min", 1);
profilingBase = p.getInt("autofetch.profiling.base", 10);
profilingMin = p.getInt("autofetch.profiling.min", profilingMin);
profilingBase = p.getInt("autofetch.profiling.base", profilingBase);
String rate = p.get("autofetch.profiling.rate", "0.05");
profilingRate = Double.parseDouble(rate);
profileUpdateFrequency = p.getInt("autofetch.profiling.updatefrequency", 60);
profilingRate = p.getDouble("autofetch.profiling.rate", profilingRate);
profileUpdateFrequency = p.getInt("autofetch.profiling.updatefrequency", profileUpdateFrequency);
}
}
@@ -1,50 +0,0 @@
package com.avaje.ebean.config;
import com.avaje.ebean.config.GlobalProperties.PropertySource;
/**
* Helper to read server specific properties from ebean.properties.
*/
class ConfigPropertyMap implements PropertySource {
private final String serverName;
public ConfigPropertyMap(String serverName) {
this.serverName = serverName;
}
public String getServerName() {
return serverName;
}
public String get(String key, String defaultValue) {
String namedKey = "ebean." + serverName + "." + key;
String inheritKey = "ebean." + key;
String value = GlobalProperties.get(namedKey, null);
if (value == null) {
value = GlobalProperties.get(inheritKey, null);
}
if (value == null) {
return defaultValue;
} else {
return value;
}
}
public int getInt(String key, int defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Integer.parseInt(value);
}
public boolean getBoolean(String key, boolean defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Boolean.parseBoolean(value);
}
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue) {
String level = get(key, defaultValue.name());
return Enum.valueOf(enumType, level.toUpperCase());
}
}
@@ -0,0 +1,464 @@
package com.avaje.ebean.config;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* Configuration for the container that holds the EbeanServer instances.
* <p>
* Provides configuration for cluster communication (if clustering is used). The cluster communication is
* used to invalidate appropriate parts of the L2 cache across the cluster.
*/
public class ContainerConfig {
/**
* Communication mode used for clustering.
*/
public enum ClusterMode {
/**
* No clustering.
*/
NONE,
/**
* Use Multicast networking for cluster wide communication.
*/
MULTICAST,
/**
* Use TCP Sockets for cluster wide communication.
*/
SOCKET
}
/**
* The cluster mode to use.
*/
ClusterMode mode = ClusterMode.NONE;
/**
* Configuration if using TCP sockets for clustering communication.
*/
SocketConfig socketConfig = new SocketConfig();
/**
* Configuration if using Multicast for clustering communication.
*/
MulticastConfig multicastConfig = new MulticastConfig();
// -------------------------------------------------------------------------------------------
// MulticastConfig
/**
* The configuration for clustering using Multicast networking.
*/
public static class MulticastConfig {
int managerSleepMillis = 80;
int lastSendTimeFreqSecs = 300;//5mins
int lastStatusTimeFreqSecs = 600;//10mins
int maxResendOutgoingAttempts = 200;
int maxResendIncomingRequests = 50;
int listenPort;
String listenAddress;
int sendPort;
String sendAddress;
// Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507
int maxSendPacketSize = 1500;
// Whether to send packets even when there are no other members online
boolean sendWithNoMembers = true;
// When multiple instances are on same box you need to broadcast back locally
boolean disableLoopback;
int listenTimeToLive = -1;
int listenTimeout = 1000;
int listenBufferSize = 65500;
// For multihomed environment the address the listener should bind to
String listenBindAddress;
/**
* Return the manager sleep millis.
*/
public int getManagerSleepMillis() {
return managerSleepMillis;
}
/**
* Set the manager sleep millis.
*/
public void setManagerSleepMillis(int managerSleepMillis) {
this.managerSleepMillis = managerSleepMillis;
}
/**
* Return the last send time frequency.
*/
public int getLastSendTimeFreqSecs() {
return lastSendTimeFreqSecs;
}
/**
* Set the last send time frequency.
*/
public void setLastSendTimeFreqSecs(int lastSendTimeFreqSecs) {
this.lastSendTimeFreqSecs = lastSendTimeFreqSecs;
}
/**
* Return the last status time frequency.
*/
public int getLastStatusTimeFreqSecs() {
return lastStatusTimeFreqSecs;
}
/**
* Set the last status time frequency.
*/
public void setLastStatusTimeFreqSecs(int lastStatusTimeFreqSecs) {
this.lastStatusTimeFreqSecs = lastStatusTimeFreqSecs;
}
/**
* Return the maximum number of times we will try to re-send a given packet before giving up sending
*/
public int getMaxResendOutgoingAttempts() {
return maxResendOutgoingAttempts;
}
/**
* Set the maximum retry attempts for outgoing messages.
*/
public void setMaxResendOutgoingAttempts(int maxResendOutgoingAttempts) {
this.maxResendOutgoingAttempts = maxResendOutgoingAttempts;
}
/**
* Return the maximum number of times we will ask for a packet to be resent to us before giving up asking.
*/
public int getMaxResendIncomingRequests() {
return maxResendIncomingRequests;
}
/**
* Set the maximum retry attempts for incoming messages.
*/
public void setMaxResendIncomingRequests(int maxResendIncomingRequests) {
this.maxResendIncomingRequests = maxResendIncomingRequests;
}
/**
* Return the listen port.
*/
public int getListenPort() {
return listenPort;
}
/**
* Set the listen port.
*/
public void setListenPort(int port) {
this.listenPort = port;
}
/**
* Return the listen address.
*/
public String getListenAddress() {
return listenAddress;
}
/**
* Set the listen address.
*/
public void setListenAddress(String listenAddress) {
this.listenAddress = listenAddress;
}
/**
* Return the send port.
*/
public int getSendPort() {
return sendPort;
}
/**
* Set the send port.
*/
public void setSendPort(int sendPort) {
this.sendPort = sendPort;
}
/**
* Return the send address.
*/
public String getSendAddress() {
return sendAddress;
}
/**
* Set the send address.
*/
public void setSendAddress(String sendAddress) {
this.sendAddress = sendAddress;
}
/**
* Return the maximum send packet size.
*/
public int getMaxSendPacketSize() {
return maxSendPacketSize;
}
/**
* Set the maximum send packet size. Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507.
*/
public void setMaxSendPacketSize(int maxSendPacketSize) {
this.maxSendPacketSize = maxSendPacketSize;
}
/**
* Return true if send messages when no other members in the cluster are up.
*/
public boolean isSendWithNoMembers() {
return sendWithNoMembers;
}
/**
* Set true if send messages when no other members in the cluster are up.
*/
public void setSendWithNoMembers(boolean sendWithNoMembers) {
this.sendWithNoMembers = sendWithNoMembers;
}
/**
* Return true if loopback is disabled. When multiple instances are on same box you need to broadcast back locally.
*/
public boolean isDisableLoopback() {
return disableLoopback;
}
/**
* Set if loopback is disabled. When multiple instances are on same box you need to broadcast back locally.
*/
public void setDisableLoopback(boolean disableLoopback) {
this.disableLoopback = disableLoopback;
}
/**
* Return the listen time to live.
*/
public int getListenTimeToLive() {
return listenTimeToLive;
}
/**
* Set the listen time to live.
*/
public void setListenTimeToLive(int listenTimeToLive) {
this.listenTimeToLive = listenTimeToLive;
}
/**
* Return the listen timeout.
*/
public int getListenTimeout() {
return listenTimeout;
}
/**
* set the listen timeout.
*/
public void setListenTimeout(int listenTimeout) {
this.listenTimeout = listenTimeout;
}
/**
* Return the listen buffer size.
*/
public int getListenBufferSize() {
return listenBufferSize;
}
/**
* Set the listen buffer size.
*/
public void setListenBufferSize(int listenBufferSize) {
this.listenBufferSize = listenBufferSize;
}
/**
* Return the listener bind address (optional). For multihomed environment the address the listener should bind to.
*/
public String getListenBindAddress() {
return listenBindAddress;
}
/**
* Set the listener bind address (optional). For multihomed environment the address the listener should bind to.
*/
public void setListenBindAddress(String listenBindAddress) {
this.listenBindAddress = listenBindAddress;
}
}
// -------------------------------------------------------------------------------------------
// SocketConfig
/**
* Configuration for clustering using TCP sockets.
* <p>
* This is good for when there are relatively small number of cluster members.
*/
public static class SocketConfig {
/**
* This local server in host:port format.
*/
String localHostPort;
/**
* All the cluster members in host:port format.
*/
List<String> members = new ArrayList<String>();
/**
* core threads for the associated thread pool.
*/
int coreThreads = 2;
/**
* Max threads for the associated thread pool.
*/
int maxThreads = 16;
String threadPoolName = "EbeanCluster";
/**
* Return the host and port for this server instance.
*/
public String getLocalHostPort() {
return localHostPort;
}
/**
* Set the host and port for this server instance.
*/
public void setLocalHostPort(String localHostPort) {
this.localHostPort = localHostPort;
}
/**
* Return all the host and port for all the members of the cluster.
*/
public List<String> getMembers() {
return members;
}
/**
* Set all the host and port for all the members of the cluster.
*/
public void setMembers(List<String> members) {
this.members = members;
}
/**
* Return the number of core threads to use.
*/
public int getCoreThreads() {
return coreThreads;
}
/**
* Set the number of core threads to use.
*/
public void setCoreThreads(int coreThreads) {
this.coreThreads = coreThreads;
}
/**
* Return the number of max threads to use.
*/
public int getMaxThreads() {
return maxThreads;
}
/**
* Set the number of max threads to use.
*/
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
}
/**
* Return the thread pool name.
*/
public String getThreadPoolName() {
return threadPoolName;
}
/**
* Set the thread pool name.
*/
public void setThreadPoolName(String threadPoolName) {
this.threadPoolName = threadPoolName;
}
}
// -------------------------------------------------------------------------------------------
// Members
/**
* Load the settings from properties.
*/
public void loadFromProperties(Properties properties) {
//TODO
}
/**
* Return the cluster mode.
*/
public ClusterMode getMode() {
return mode;
}
/**
* Set the cluster mode.
*/
public void setMode(ClusterMode mode) {
this.mode = mode;
}
/**
* Return the socket communication configuration.
*/
public SocketConfig getSocketConfig() {
return socketConfig;
}
/**
* Set the socket communication configuration.
*/
public void setSocketConfig(SocketConfig socketConfig) {
this.socketConfig = socketConfig;
}
/**
* Return the multicast communication configuration.
*/
public MulticastConfig getMulticastConfig() {
return multicastConfig;
}
/**
* Set the multicast communication configuration.
*/
public void setMulticastConfig(MulticastConfig multicastConfig) {
this.multicastConfig = multicastConfig;
}
}
@@ -2,6 +2,7 @@ package com.avaje.ebean.config;
import java.sql.Connection;
import java.util.Map;
import java.util.Properties;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.util.StringHelper;
@@ -448,48 +449,63 @@ public class DataSourceConfig {
this.customProperties = customProperties;
}
/**
* Load the settings by reading the ebean.properties file.
*
* @param serverName name of the server
*/
public void loadSettings(String serverName) {
loadSettingsCustomPrefix("datasource." + serverName + ".", new GlobalProperties.DelegatedGlobalPropertySource(serverName));
loadSettings(new PropertiesWrapper("datasource", serverName, PropertyMap.defaultProperties()));
}
/**
* Load the settings from ebean.properties.
* Load the settings from the properties supplied.
* <p>
* You can use this when you have your own properties to use for configuration.
* </p>
*
* @param properties the properties to configure the datasource
* @param serverName the name of the specific datasource (optional)
*/
public void loadSettingsCustomPrefix(String prefix, GlobalProperties.PropertySource properties) {
public void loadSettings(Properties properties, String serverName) {
PropertiesWrapper dbProps = new PropertiesWrapper("datasource", serverName, properties);
loadSettings(dbProps);
}
this.username = properties.get(prefix + "username", null);
this.password = properties.get(prefix + "password", null);
/**
* Load the settings from the PropertiesWrapper.
*/
public void loadSettings(PropertiesWrapper properties) {
String dbDriver = properties.get(prefix + "databaseDriver", null);
this.driver = properties.get(prefix + "driver", dbDriver);
username = properties.get("username", username);
password = properties.get("password", password);
driver = properties.get("driver", properties.get("databaseDriver", driver));
url = properties.get("url", properties.get("databaseUrl", url));
String dbUrl = properties.get(prefix + "databaseUrl", null);
this.url = properties.get(prefix + "url", dbUrl);
autoCommit = properties.getBoolean("autoCommit", autoCommit);
captureStackTrace = properties.getBoolean("captureStackTrace", captureStackTrace);
maxStackTraceSize = properties.getInt("maxStackTraceSize", maxStackTraceSize);
leakTimeMinutes = properties.getInt("leakTimeMinutes", leakTimeMinutes);
maxInactiveTimeSecs = properties.getInt("maxInactiveTimeSecs", maxInactiveTimeSecs);
trimPoolFreqSecs = properties.getInt("trimPoolFreqSecs", trimPoolFreqSecs);
maxAgeMinutes = properties.getInt("maxAgeMinutes", maxAgeMinutes);
this.autoCommit = properties.getBoolean(prefix + "autoCommit", false);
this.captureStackTrace = properties.getBoolean(prefix + "captureStackTrace", false);
this.maxStackTraceSize = properties.getInt(prefix + "maxStackTraceSize", 5);
this.leakTimeMinutes = properties.getInt(prefix + "leakTimeMinutes", 30);
this.maxInactiveTimeSecs = properties.getInt(prefix + "maxInactiveTimeSecs", 720);
this.trimPoolFreqSecs = properties.getInt(prefix + "trimPoolFreqSecs", 59);
this.maxAgeMinutes = properties.getInt(prefix + "maxAgeMinutes", 0);
minConnections = properties.getInt("minConnections", minConnections);
maxConnections = properties.getInt("maxConnections", maxConnections);
pstmtCacheSize = properties.getInt("pstmtCacheSize", pstmtCacheSize);
cstmtCacheSize = properties.getInt("cstmtCacheSize", cstmtCacheSize);
this.minConnections = properties.getInt(prefix + "minConnections", 0);
this.maxConnections = properties.getInt(prefix + "maxConnections", 20);
this.pstmtCacheSize = properties.getInt(prefix + "pstmtCacheSize", 20);
this.cstmtCacheSize = properties.getInt(prefix + "cstmtCacheSize", 20);
waitTimeoutMillis = properties.getInt("waitTimeout", waitTimeoutMillis);
this.waitTimeoutMillis = properties.getInt(prefix + "waitTimeout", 1000);
heartbeatSql = properties.get("heartbeatSql", heartbeatSql);
heartbeatTimeoutSeconds = properties.getInt("heartbeatTimeoutSeconds", heartbeatTimeoutSeconds);
poolListener = properties.get("poolListener", poolListener);
offline = properties.getBoolean("offline", offline);
this.heartbeatSql = properties.get(prefix + "heartbeatSql", null);
this.heartbeatTimeoutSeconds = properties.getInt(prefix + "heartbeatTimeoutSeconds", 3);
this.poolListener = properties.get(prefix + "poolListener", null);
this.offline = properties.getBoolean(prefix + "offline", false);
String isoLevel = properties.get(prefix + "isolationlevel", "READ_COMMITTED");
String isoLevel = properties.get("isolationlevel", getTransactionIsolationLevel(isolationLevel));
this.isolationLevel = getTransactionIsolationLevel(isoLevel);
String customProperties = properties.get(prefix + "customProperties", null);
String customProperties = properties.get("customProperties", null);
if (customProperties != null && customProperties.length() > 0) {
Map<String, String> custProps = StringHelper.delimitedToMap(customProperties, ";", "=");
this.customProperties = custProps;
@@ -498,7 +514,21 @@ public class DataSourceConfig {
}
/**
* return the isolation level for a given string description.
* Return the isolation level description from the associated Connection int value.
*/
public String getTransactionIsolationLevel(int level) {
switch (level) {
case Connection.TRANSACTION_NONE : return "NONE";
case Connection.TRANSACTION_READ_COMMITTED : return "READ_COMMITTED";
case Connection.TRANSACTION_READ_UNCOMMITTED : return "READ_UNCOMMITTED";
case Connection.TRANSACTION_REPEATABLE_READ : return "REPEATABLE_READ";
case Connection.TRANSACTION_SERIALIZABLE : return "SERIALIZABLE";
default: throw new RuntimeException("Transaction Isolation level [" + level + "] is not known.");
}
}
/**
* Return the isolation level for a given string description.
*/
public int getTransactionIsolationLevel(String level) {
level = level.toUpperCase();
@@ -522,6 +552,6 @@ public class DataSourceConfig {
return Connection.TRANSACTION_SERIALIZABLE;
}
throw new RuntimeException("Transaction Isolaction level [" + level + "] is not known.");
throw new RuntimeException("Transaction Isolation level [" + level + "] is not known.");
}
}
@@ -1,211 +0,0 @@
package com.avaje.ebean.config;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.ServletContext;
import com.avaje.ebean.util.ClassUtil;
/**
* Provides access to properties loaded from the ebean.properties file.
*/
public final class GlobalProperties {
private static volatile PropertyMap globalMap;
private static boolean skipPrimaryServer;
/**
* Set whether to skip automatically creating the primary server.
*/
public static synchronized void setSkipPrimaryServer(boolean skip) {
skipPrimaryServer = skip;
}
/**
* Return true to skip automatically creating the primary server.
*/
public static synchronized boolean isSkipPrimaryServer() {
return skipPrimaryServer;
}
/**
* Parse the string replacing any expressions like ${catalina.base}.
* <p>
* This will evaluate expressions using first environment variables, than java
* system variables and lastly properties in ebean.properties - in that order.
* </p>
* <p>
* Expressions start with "${" and end with "}".
* </p>
*/
public static String evaluateExpressions(String val) {
return getPropertyMap().eval(val);
}
/**
* Parse and evaluate any expressions that have not already been evaluated.
*/
public static synchronized void evaluateExpressions() {
getPropertyMap().evaluateProperties();
}
/**
* In a servlet container environment this will additionally look in WEB-INF
* for the ebean.properties file.
*/
public static synchronized void setServletContext(ServletContext servletContext) {
PropertyMapLoader.setServletContext(servletContext);
}
/**
* Return the ServletContext (if setup in a servlet container environment).
*/
public static synchronized ServletContext getServletContext() {
return PropertyMapLoader.getServletContext();
}
private static void initPropertyMap() {
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName == null) {
fileName = "ebean.properties";
}
}
globalMap = PropertyMapLoader.load(null, fileName);
if (globalMap == null) {
// ebean.properties file was not found... but that
// is ok because we are likely doing programmatic config
globalMap = new PropertyMap();
}
String loaderCn = globalMap.get("ebean.properties.loader");
if (loaderCn != null) {
// a Runnable that can be used to customise the initialisation
// of the GlobalProperties
try {
Runnable r = (Runnable) ClassUtil.newInstance(loaderCn);
r.run();
} catch (Exception e) {
String m = "Error creating or running properties loader " + loaderCn;
throw new RuntimeException(m, e);
}
}
}
/**
* Return the property map loading it if required.
*/
private static synchronized PropertyMap getPropertyMap() {
if (globalMap == null) {
initPropertyMap();
}
return globalMap;
}
/**
* Return a String property with a default value.
*/
public static synchronized String get(String key, String defaultValue) {
return getPropertyMap().get(key, defaultValue);
}
/**
* Return a int property with a default value.
*/
public static synchronized int getInt(String key, int defaultValue) {
return getPropertyMap().getInt(key, defaultValue);
}
/**
* Return a boolean property with a default value.
*/
public static synchronized boolean getBoolean(String key, boolean defaultValue) {
return getPropertyMap().getBoolean(key, defaultValue);
}
/**
* Set a property return the previous value. This will evaluate any
* expressions in the value.
*/
public static synchronized String put(String key, String value) {
return getPropertyMap().putEval(key, value);
}
/**
* Set a Map of key value properties.
*/
public static synchronized void putAll(Map<String, String> keyValueMap) {
for (Entry<String, String> e : keyValueMap.entrySet()) {
getPropertyMap().putEval(e.getKey(), e.getValue());
}
}
public static PropertySource getPropertySource(String name) {
return new ConfigPropertyMap(name);
}
public static interface PropertySource {
/**
* Return the name of the server. This is also the dataSource name.
*/
public String getServerName();
/**
* Get a property. This will prepend "ebean" and the server name to lookup
* the value.
*/
public String get(String key, String defaultValue);
public int getInt(String key, int defaultValue);
public boolean getBoolean(String key, boolean defaultValue);
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue);
}
public static class DelegatedGlobalPropertySource implements PropertySource {
private String serverName;
public DelegatedGlobalPropertySource(String serverName) {
this.serverName = serverName;
}
@Override
public String getServerName() {
return serverName;
}
@Override
public String get(String key, String defaultValue) {
return GlobalProperties.get(key, defaultValue);
}
@Override
public int getInt(String key, int defaultValue) {
return GlobalProperties.getInt(key, defaultValue);
}
@Override
public boolean getBoolean(String key, boolean defaultValue) {
return GlobalProperties.getBoolean(key, defaultValue);
}
@Override
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue) {
String level = get(key, defaultValue.name());
return Enum.valueOf(enumType, level.toUpperCase());
}
}
}
@@ -0,0 +1,29 @@
package com.avaje.ebean.config;
/**
* Configuration for JSON features.
*/
public abstract class JsonConfig {
/**
* Defined the format used for DateTime types.
*/
public enum DateTime {
/**
* Format as epoch millis.
*/
MILLIS,
/**
* Format as epoch with nanos.
*/
NANOS,
/**
* Format as ISO-8601 date format.
*/
ISO8601
}
}
@@ -112,4 +112,9 @@ public interface NamingConvention {
*/
public boolean isUseForeignKeyPrefix();
/**
* Load setting from properties.
*/
public void loadFromProperties(PropertiesWrapper properties);
}
@@ -0,0 +1,52 @@
package com.avaje.ebean.config;
/**
* Defines the mode for JDBC batch processing.
* <p>
* Used both at a per transaction basis and per request basis.
* </p>
*
* @see com.avaje.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
* @see com.avaje.ebean.config.ServerConfig#setPersistBatchOnCascade(PersistBatch)
*
* @see com.avaje.ebean.Transaction#setBatch(PersistBatch)
* @see com.avaje.ebean.Transaction#setBatchOnCascade(PersistBatch)
*/
public enum PersistBatch {
/**
* Do not use JDBC Batch mode.
*/
NONE(false),
/**
* Use JDBC Batch mode on Inserts.
*/
INSERT(true),
/**
* Use JDBC Batch mode on Inserts, Updates and Deletes.
*/
ALL(true),
/**
* You should not use this value explicitly. It should only used on the Transactional annotation
* to indicate that the value should inherit from the ServerConfig setting.
*/
INHERIT(false);
boolean forInsert;
PersistBatch(boolean forInsert) {
this.forInsert = forInsert;
}
/**
* Return true if persist cascade should use JDBC batch for inserts.
*/
public boolean forInsert() {
return forInsert;
}
}
@@ -0,0 +1,148 @@
package com.avaje.ebean.config;
import java.util.Properties;
public class PropertiesWrapper {
protected final Properties properties;
protected final String prefix;
protected final String serverName;
protected final PropertyMap propertyMap;
/**
* Construct with a prefix, serverName and properties.
*/
public PropertiesWrapper(String prefix, String serverName, Properties properties) {
this.serverName = serverName;
this.prefix = prefix;
this.propertyMap = PropertyMapLoader.load(null, properties);
this.properties = propertyMap.asProperties();
}
/**
* Construct without prefix of serverName.
*/
public PropertiesWrapper(Properties properties) {
this(null, null, properties);
}
/**
* Internal copy constructor when changing prefix.
*/
protected PropertiesWrapper(String prefix, String serverName, PropertyMap propertyMap, Properties properties) {
this.serverName = serverName;
this.prefix = prefix;
this.propertyMap = propertyMap;
this.properties = properties;
}
/**
* Return a PropertiesWrapper instance with a different prefix but same underlying properties.
* <p/>
* Used when wanting to use "datasource" as the prefix rather than "ebean".
* <p/>
* The returning instance should only be used in a read only fashion.
*/
public PropertiesWrapper withPrefix(String prefix) {
return new PropertiesWrapper(prefix, serverName, propertyMap, properties);
}
/**
* Return the serverName (optional).
*/
public String getServerName() {
return serverName;
}
/**
* Return as Properties with lower case keys and after evaluation and additional properties loading has occurred.
* <p>
* Ebean has historically ignored the case of keys hence returning the Properties with all the keys lower cased.
* </p>
*/
public Properties asPropertiesLowerCase() {
return properties;
}
/**
* Get a property with no default value.
*/
public String get(String key) {
return get(key, null);
}
/**
* Get a property with a default value.
* <p>
* This performs a search using the prefix and server name (if supplied) to search for the property
* value in order based on:
* <pre>{@code
* prefix.serverName.key
* prefix.key
* key
* }</pre>
* </p>
*/
public String get(String key, String defaultValue) {
String value = null;
if (serverName != null && prefix != null) {
value = propertyMap.get(prefix + "." + serverName + "." + key, null);
}
if (value == null && prefix != null) {
value = propertyMap.get(prefix + "." + key, null);
}
if (value == null) {
value = propertyMap.get(key, null);
}
return value == null ? defaultValue : value;
}
/**
* Return a double property value.
*/
public double getDouble(String key, double defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Double.parseDouble(value);
}
/**
* Return an int property value.
*/
public int getInt(String key, int defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Integer.parseInt(value);
}
/**
* Return a long property value.
*/
public long getLong(String key, long defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Long.parseLong(value);
}
/**
* Return a boolean property value.
*/
public boolean getBoolean(String key, boolean defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Boolean.parseBoolean(value);
}
/**
* Return a Enum property value.
*/
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue) {
String level = get(key, defaultValue.name());
return Enum.valueOf(enumType, level.toUpperCase());
}
}
@@ -4,21 +4,40 @@ import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
/**
* A map like structure of properties.
* <p/>
* Handles evaluation of expressions like ${home} and provides convenience methods for int, long and boolean.
*/
final class PropertyMap implements Serializable {
public final class PropertyMap implements Serializable {
private static final long serialVersionUID = 1L;
private LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
public static Properties defaultProperties() {
PropertyMap propertyMap = PropertyMapLoader.loadGlobalProperties();
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
}
public String toString() {
return map.toString();
}
/**
* Return as standard Properties.
*/
public Properties asProperties() {
Properties properties = new Properties();
for (Entry<String, String> e : entrySet()) {
properties.put(e.getKey(), e.getValue());
}
return properties;
}
/**
* Go through all the properties and evaluate any expressions that have not
* been resolved.
@@ -35,10 +54,16 @@ final class PropertyMap implements Serializable {
}
}
/**
* Returns the value with expressions like ${home} evaluated using system properties and environment variables.
*/
public synchronized String eval(String val) {
return PropertyExpression.eval(val, this);
}
/**
* Return the boolean property value with a given default.
*/
public synchronized boolean getBoolean(String key, boolean defaultValue) {
String value = get(key);
if (value == null) {
@@ -48,6 +73,9 @@ final class PropertyMap implements Serializable {
}
}
/**
* Return the int property value with a given default.
*/
public synchronized int getInt(String key, int defaultValue) {
String value = get(key);
if (value == null) {
@@ -57,36 +85,69 @@ final class PropertyMap implements Serializable {
}
}
/**
* Return the long property value with a given default.
*/
public synchronized long getLong(String key, long defaultValue) {
String value = get(key);
if (value == null) {
return defaultValue;
} else {
return Long.parseLong(value);
}
}
/**
* Return the string property value with a given default.
*/
public synchronized String get(String key, String defaultValue) {
String value = map.get(key.toLowerCase());
return value == null ? defaultValue : value;
}
/**
* Return the property value returning null if there is no value defined.
*/
public synchronized String get(String key) {
return map.get(key.toLowerCase());
}
synchronized void putAll(Map<String, String> keyValueMap) {
/**
* Put all evaluating any expressions in the values.
*/
public synchronized void putEvalAll(Map<String, String> keyValueMap) {
for (Map.Entry<String, String> entry : keyValueMap.entrySet()) {
put(entry.getKey(), entry.getValue());
putEval(entry.getKey(), entry.getValue());
}
}
synchronized String putEval(String key, String value) {
/**
* Put a single key value evaluating any expressions in the value.
*/
public synchronized String putEval(String key, String value) {
value = PropertyExpression.eval(value, this);
return map.put(key.toLowerCase(), value);
}
synchronized String put(String key, String value) {
/**
* Put a single key value with no expression evaluation.
*/
public synchronized String put(String key, String value) {
return map.put(key.toLowerCase(), value);
}
synchronized String remove(String key) {
/**
* Remove an entry.
*/
public synchronized String remove(String key) {
return map.remove(key.toLowerCase());
}
synchronized Set<Entry<String, String>> entrySet() {
/**
* Return the entries.
*/
public synchronized Set<Entry<String, String>> entrySet() {
return map.entrySet();
}
@@ -1,40 +1,30 @@
package com.avaje.ebean.config;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.Map;
import java.util.Properties;
/**
* Helper used to load the PropertyMap.
* Helper used to load the ebean.properties into a PropertyMap.
*/
final class PropertyMapLoader {
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
private static ServletContext servletContext;
public static PropertyMap loadGlobalProperties() {
/**
* Return the servlet context when in a web environment.
*/
public static ServletContext getServletContext() {
return servletContext;
}
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName == null) {
fileName = "ebean.properties";
}
}
/**
* Set the ServletContext for when ebean.properties is in WEB-INF in a web
* application environment.
*/
public static void setServletContext(ServletContext servletContext) {
PropertyMapLoader.servletContext = servletContext;
return load(null, fileName);
}
/**
@@ -64,15 +54,20 @@ final class PropertyMapLoader {
* @param in
* the InputStream of the properties file to load.
*/
private static PropertyMap load(PropertyMap p, InputStream in) {
public static PropertyMap load(PropertyMap p, InputStream in) {
Properties props = new Properties();
try {
props.load(in);
in.close();
return load(p, props);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static PropertyMap load(PropertyMap p, Properties props) {
if (p == null) {
p = new PropertyMap();
@@ -117,18 +112,6 @@ final class PropertyMapLoader {
throw new NullPointerException("fileName is null?");
}
if (servletContext == null) {
logger.debug("No servletContext so not looking in WEB-INF for " + fileName);
} else {
// first look in WEB-INF ...
InputStream in = servletContext.getResourceAsStream("/WEB-INF/" + fileName);
if (in != null) {
logger.debug(fileName + " found in WEB-INF");
return in;
}
}
try {
File f = new File(fileName);
File diff suppressed because it is too large Load Diff
@@ -1,302 +1,446 @@
package com.avaje.ebean.config.dbplatform;
import java.sql.Types;
import javax.sql.DataSource;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Database platform specific settings.
*/
public class DatabasePlatform {
/** The Constant logger. */
private static final Logger logger = LoggerFactory.getLogger(DatabasePlatform.class);
/** The open quote used by quoted identifiers. */
protected String openQuote = "\"";
/** The close quote used by quoted identifiers. */
protected String closeQuote = "\"";
/** For limit/offset, row_number etc limiting of SQL queries. */
protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter();
/** Mapping of JDBC to Database types. */
protected DbTypeMap dbTypeMap = new DbTypeMap();
/** DB specific DDL syntax. */
protected DbDdlSyntax dbDdlSyntax = new DbDdlSyntax();
/** Defines DB identity/sequence features. */
protected DbIdentity dbIdentity = new DbIdentity();
/** The JDBC type to map booleans to (by default). */
protected int booleanDbType = Types.BOOLEAN;
/** The JDBC type to map Blob to. */
protected int blobDbType = Types.BLOB;
/** The JDBC type to map Clob to. */
protected int clobDbType = Types.CLOB;
/** For Oracle treat empty strings as null. */
protected boolean treatEmptyStringsAsNull;
/** The name. */
protected String name = "generic";
/**
* Use a BackTick ` at the beginning and end of table or column names that you
* want to use quoted identifiers for. The backticks get converted to the
* appropriate characters in convertQuotedIdentifiers
*/
private static final char BACK_TICK = '`';
protected DbEncrypt dbEncrypt;
protected boolean idInExpandedForm;
protected boolean selectCountWithAlias;
/**
* If set then use the FORWARD ONLY hint when creating ResultSets for
* findIterate() and findVisit().
*/
protected boolean forwardOnlyHintOnFindIterate;
/**
* Instantiates a new database platform.
*/
public DatabasePlatform() {
}
/**
* Return the name of the DatabasePlatform.
* <p>
* "generic" is returned when no specific database platform has been set or
* found.
* </p>
*
* @return the name
*/
public String getName() {
return name;
}
/**
* Return a DB Sequence based IdGenerator.
*
* @param be
* the BackgroundExecutor that can be used to load the sequence if
* desired
* @param ds
* the DataSource
* @param seqName
* the name of the sequence
* @param batchSize
* the number of sequences that should be loaded
*/
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
String seqName, int batchSize) {
return null;
}
/**
* Return the DbEncrypt handler for this DB platform.
*/
public DbEncrypt getDbEncrypt() {
return dbEncrypt;
}
/**
* Set the DbEncrypt handler for this DB platform.
*/
public void setDbEncrypt(DbEncrypt dbEncrypt) {
this.dbEncrypt = dbEncrypt;
}
/**
* Return the mapping of JDBC to DB types.
*
* @return the db type map
*/
public DbTypeMap getDbTypeMap() {
return dbTypeMap;
}
/**
* Return the DDL syntax for this platform.
*
* @return the db ddl syntax
*/
public DbDdlSyntax getDbDdlSyntax() {
return dbDdlSyntax;
}
/**
* Return the close quote for quoted identifiers.
*
* @return the close quote
*/
public String getCloseQuote() {
return closeQuote;
}
/**
* Return the open quote for quoted identifiers.
*
* @return the open quote
*/
public String getOpenQuote() {
return openQuote;
}
/**
* Return the JDBC type used to store booleans.
*
* @return the boolean db type
*/
public int getBooleanDbType() {
return booleanDbType;
}
/**
* Return the data type that should be used for Blob.
* <p>
* This is typically Types.BLOB but for Postgres is Types.LONGVARBINARY for
* example.
* </p>
*/
public int getBlobDbType() {
return blobDbType;
}
/**
* Return the data type that should be used for Clob.
* <p>
* This is typically Types.CLOB but for Postgres is Types.VARCHAR.
* </p>
*/
public int getClobDbType() {
return clobDbType;
}
/**
* Return true if empty strings should be treated as null.
*
* @return true, if checks if is treat empty strings as null
*/
public boolean isTreatEmptyStringsAsNull() {
return treatEmptyStringsAsNull;
}
/**
* Return true if a compound ID in (...) type expression needs to be in
* expanded form of (a=? and b=?) or (a=? and b=?) or ... rather than (a,b) in
* ((?,?),(?,?),...);
*/
public boolean isIdInExpandedForm() {
return idInExpandedForm;
}
/**
* Return true if the ResultSet TYPE_FORWARD_ONLY Hint should be used on
* findIterate() and findVisit() PreparedStatements.
* <p>
* This specifically is required for MySql when processing large results.
* </p>
*/
public boolean isForwardOnlyHintOnFindIterate() {
return forwardOnlyHintOnFindIterate;
}
/**
* Set to true if the ResultSet TYPE_FORWARD_ONLY Hint should be used by default on findIterate PreparedStatements.
*/
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
}
/**
* Return the DB identity/sequence features for this platform.
*
* @return the db identity
*/
public DbIdentity getDbIdentity() {
return dbIdentity;
}
/**
* Return the SqlLimiter used to apply additional sql around a query to limit
* its results.
* <p>
* Basically add the clauses for limit/offset, rownum, row_number().
* </p>
*
* @return the sql limiter
*/
public SqlLimiter getSqlLimiter() {
return sqlLimiter;
}
/**
* Convert backticks to the platform specific open quote and close quote
*
* <p>
* Specific plugins may implement this method to cater for platform specific
* naming rules.
* </p>
*
* @param dbName
* the db name
*
* @return the string
*/
public String convertQuotedIdentifiers(String dbName) {
// Ignore null values e.g. schema name or catalog
if (dbName != null && dbName.length() > 0) {
if (dbName.charAt(0) == BACK_TICK) {
if (dbName.charAt(dbName.length() - 1) == BACK_TICK) {
String quotedName = getOpenQuote();
quotedName += dbName.substring(1, dbName.length() - 1);
quotedName += getCloseQuote();
return quotedName;
} else {
logger.error("Missing backquote on [" + dbName + "]");
}
}
}
return dbName;
}
/**
* Set to true if select count against anonymous view requires an alias.
*/
public boolean isSelectCountWithAlias() {
return selectCountWithAlias;
}
public String completeSql(String sql, Query<?> query) {
if (Boolean.TRUE.equals(query.isForUpdate())) {
sql = withForUpdate(sql);
}
return sql;
}
protected String withForUpdate(String sql) {
// silently assume the database does not support the "for update" clause.
logger.info("it seems your database does not support the 'for update' clause");
return sql;
}
}
package com.avaje.ebean.config.dbplatform;
import java.sql.Types;
import javax.sql.DataSource;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Database platform specific settings.
*/
public class DatabasePlatform {
private static final Logger logger = LoggerFactory.getLogger(DatabasePlatform.class);
/**
* Behavior used when ending a query only transaction (at read committed isolation level).
*/
public enum OnQueryOnly {
/**
* Rollback the transaction.
*/
ROLLBACK,
/**
* Just close the transaction. Valid at READ_COMMITTED isolation and preferred on some Databases
* as a performance optimisation.
*/
CLOSE,
/**
* Commit the transaction
*/
COMMIT
}
/**
* The behaviour used when ending a read only transaction at read committed isolation level.
*/
protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK;
/**
* The open quote used by quoted identifiers.
*/
protected String openQuote = "\"";
/**
* The close quote used by quoted identifiers.
*/
protected String closeQuote = "\"";
/**
* For limit/offset, row_number etc limiting of SQL queries.
*/
protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter();
/**
* Mapping of JDBC to Database types.
*/
protected DbTypeMap dbTypeMap = new DbTypeMap();
/**
* DB specific DDL syntax.
*/
protected DbDdlSyntax dbDdlSyntax = new DbDdlSyntax();
/**
* Defines DB identity/sequence features.
*/
protected DbIdentity dbIdentity = new DbIdentity();
/**
* The JDBC type to map booleans to (by default).
*/
protected int booleanDbType = Types.BOOLEAN;
/**
* The JDBC type to map Blob to.
*/
protected int blobDbType = Types.BLOB;
/**
* The JDBC type to map Clob to.
*/
protected int clobDbType = Types.CLOB;
/**
* For Oracle treat empty strings as null.
*/
protected boolean treatEmptyStringsAsNull;
/**
* The database platform name.
*/
protected String name = "generic";
protected String columnAliasPrefix = "c";
protected String tableAliasPlaceHolder = "${ta}";
/**
* Use a BackTick ` at the beginning and end of table or column names that you
* want to use quoted identifiers for. The backticks get converted to the
* appropriate characters in convertQuotedIdentifiers
*/
private static final char BACK_TICK = '`';
/**
* The like clause. Can be overridden to disable default escape character.
*/
protected String likeClause = "like ?";
protected DbEncrypt dbEncrypt;
protected boolean idInExpandedForm;
protected boolean selectCountWithAlias;
/**
* If set then use the FORWARD ONLY hint when creating ResultSets for
* findIterate() and findVisit().
*/
protected boolean forwardOnlyHintOnFindIterate;
/**
* Flag set for SQL Server due to lack of support of getGeneratedKeys in
* batch mode (meaning for batch inserts you should explicitly turn off
* getGeneratedKeys - joy).
*/
protected boolean disallowBatchOnCascade;
/**
* Instantiates a new database platform.
*/
public DatabasePlatform() {
}
/**
* Return the name of the DatabasePlatform.
* <p>
* "generic" is returned when no specific database platform has been set or
* found.
* </p>
*
* @return the name
*/
public String getName() {
return name;
}
/**
* Return a DB Sequence based IdGenerator.
*
* @param be
* the BackgroundExecutor that can be used to load the sequence if
* desired
* @param ds
* the DataSource
* @param seqName
* the name of the sequence
* @param batchSize
* the number of sequences that should be loaded
*/
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
return null;
}
/**
* Return the behaviour to use when ending a read only transaction.
*/
public OnQueryOnly getOnQueryOnly() {
return onQueryOnly;
}
/**
* Set the behaviour to use when ending a read only transaction.
*/
public void setOnQueryOnly(OnQueryOnly onQueryOnly) {
this.onQueryOnly = onQueryOnly;
}
/**
* Return the DbEncrypt handler for this DB platform.
*/
public DbEncrypt getDbEncrypt() {
return dbEncrypt;
}
/**
* Set the DbEncrypt handler for this DB platform.
*/
public void setDbEncrypt(DbEncrypt dbEncrypt) {
this.dbEncrypt = dbEncrypt;
}
/**
* Return the mapping of JDBC to DB types.
*
* @return the db type map
*/
public DbTypeMap getDbTypeMap() {
return dbTypeMap;
}
/**
* Return the DDL syntax for this platform.
*
* @return the db ddl syntax
*/
public DbDdlSyntax getDbDdlSyntax() {
return dbDdlSyntax;
}
/**
* Return the column alias prefix.
*/
public String getColumnAliasPrefix() {
return columnAliasPrefix;
}
/**
* Set the column alias prefix.
*/
public void setColumnAliasPrefix(String columnAliasPrefix) {
this.columnAliasPrefix = columnAliasPrefix;
}
/**
* Return the table alias placeholder.
*/
public String getTableAliasPlaceHolder() {
return tableAliasPlaceHolder;
}
/**
* Set the table alias placeholder.
*/
public void setTableAliasPlaceHolder(String tableAliasPlaceHolder) {
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
}
/**
* Return the close quote for quoted identifiers.
*
* @return the close quote
*/
public String getCloseQuote() {
return closeQuote;
}
/**
* Return the open quote for quoted identifiers.
*
* @return the open quote
*/
public String getOpenQuote() {
return openQuote;
}
/**
* Return the JDBC type used to store booleans.
*
* @return the boolean db type
*/
public int getBooleanDbType() {
return booleanDbType;
}
/**
* Return the data type that should be used for Blob.
* <p>
* This is typically Types.BLOB but for Postgres is Types.LONGVARBINARY for
* example.
* </p>
*/
public int getBlobDbType() {
return blobDbType;
}
/**
* Return the data type that should be used for Clob.
* <p>
* This is typically Types.CLOB but for Postgres is Types.VARCHAR.
* </p>
*/
public int getClobDbType() {
return clobDbType;
}
/**
* Return true if empty strings should be treated as null.
*
* @return true, if checks if is treat empty strings as null
*/
public boolean isTreatEmptyStringsAsNull() {
return treatEmptyStringsAsNull;
}
/**
* Return true if a compound ID in (...) type expression needs to be in
* expanded form of (a=? and b=?) or (a=? and b=?) or ... rather than (a,b) in
* ((?,?),(?,?),...);
*/
public boolean isIdInExpandedForm() {
return idInExpandedForm;
}
/**
* Return true if the ResultSet TYPE_FORWARD_ONLY Hint should be used on
* findIterate() and findVisit() PreparedStatements.
* <p>
* This specifically is required for MySql when processing large results.
* </p>
*/
public boolean isForwardOnlyHintOnFindIterate() {
return forwardOnlyHintOnFindIterate;
}
/**
* Set to true if the ResultSet TYPE_FORWARD_ONLY Hint should be used by default on findIterate PreparedStatements.
*/
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
}
/**
* Return the DB identity/sequence features for this platform.
*
* @return the db identity
*/
public DbIdentity getDbIdentity() {
return dbIdentity;
}
/**
* Return the SqlLimiter used to apply additional sql around a query to limit
* its results.
* <p>
* Basically add the clauses for limit/offset, rownum, row_number().
* </p>
*
* @return the sql limiter
*/
public SqlLimiter getSqlLimiter() {
return sqlLimiter;
}
/**
* Convert backticks to the platform specific open quote and close quote
*
* <p>
* Specific plugins may implement this method to cater for platform specific
* naming rules.
* </p>
*
* @param dbName
* the db name
*
* @return the string
*/
public String convertQuotedIdentifiers(String dbName) {
// Ignore null values e.g. schema name or catalog
if (dbName != null && dbName.length() > 0) {
if (dbName.charAt(0) == BACK_TICK) {
if (dbName.charAt(dbName.length() - 1) == BACK_TICK) {
String quotedName = getOpenQuote();
quotedName += dbName.substring(1, dbName.length() - 1);
quotedName += getCloseQuote();
return quotedName;
} else {
logger.error("Missing backquote on [" + dbName + "]");
}
}
}
return dbName;
}
/**
* Set to true if select count against anonymous view requires an alias.
*/
public boolean isSelectCountWithAlias() {
return selectCountWithAlias;
}
public String completeSql(String sql, Query<?> query) {
if (Boolean.TRUE.equals(query.isForUpdate())) {
sql = withForUpdate(sql);
}
return sql;
}
protected String withForUpdate(String sql) {
// silently assume the database does not support the "for update" clause.
logger.info("it seems your database does not support the 'for update' clause");
return sql;
}
/**
* Returns the like clause used by this database platform.
* <p>
* This may include an escape clause to disable a default escape character.
*/
public String getLikeClause() {
return likeClause;
}
/**
* Return true if the persistBatchOnCascade setting should be ignored.
* <p>
* This is primarily for SQL Server which does not support getGeneratedKeys with jdbc batch mode
* so can't really be transparently used.
* </p>
*/
public boolean isDisallowBatchOnCascade() {
return disallowBatchOnCascade;
}
/**
* Generate and return the create sequence DDL.
*/
public String ddlCreateSequence(String sequenceName, int initialValue, int allocationSize) {
StringBuilder sb = new StringBuilder("create sequence ");
sb.append(sequenceName);
if (initialValue > 1) {
sb.append(" start with ").append(initialValue);
}
if (allocationSize > 0 && allocationSize != 50) {
// at this stage ignoring allocationSize 50 as this is the 'default' and
// not consistent with the way Ebean batch fetches sequence values
sb.append(" increment by ").append(allocationSize);
}
sb.append(";");
return sb.toString();
}
}
@@ -7,30 +7,35 @@ import com.avaje.ebean.config.TableName;
*/
public class DbDdlSyntax {
private boolean renderIndexForFkey = true;
protected boolean renderIndexForFkey = true;
private boolean inlinePrimaryKeyConstraint = false;
protected boolean inlinePrimaryKeyConstraint = false;
private boolean addOneToOneUniqueContraint = true;
protected boolean addOneToOneUniqueContraint = true;
private int maxConstraintNameLength = 32;
protected int maxConstraintNameLength = 32;
private int columnNameWidth = 25;
protected int columnNameWidth = 25;
private String dropTableCascade;
private String dropIfExists;
/**
* Flag set when we want to disable constraints on each table (rather than globally).
*/
protected boolean dropKeyConstraints;
private String newLine = "\n";
protected String dropTableCascade;
protected String dropIfExists;
private String identity = "auto_increment";
private String identitySuffix = "";
protected String newLine = "\n";
private String pkPrefix = "pk_";
protected String identity = "auto_increment";
protected String identitySuffix = "";
private String disableReferentialIntegrity;
private String enableReferentialIntegrity;
protected String pkPrefix = "pk_";
private String foreignKeySuffix;
protected String disableReferentialIntegrity;
protected String enableReferentialIntegrity;
protected String foreignKeySuffix;
/**
* Return the primary key name for a given bean descriptor.
@@ -158,6 +163,20 @@ public class DbDdlSyntax {
this.enableReferentialIntegrity = enableReferentialIntegrity;
}
/**
* Return true to is constraints are disabled on each table.
*/
public boolean isDropKeyConstraints() {
return dropKeyConstraints;
}
/**
* Return some DDL to disable constraints on the given table.
*/
public String dropKeyConstraintPrefix(String tableName, String fkName) {
return null;
}
/**
* Return true if indexes should be created for the foreign keys.
*/
@@ -188,6 +207,13 @@ public class DbDdlSyntax {
this.dropIfExists = dropIfExists;
}
/**
* Return prefix text that goes before drop table.
*/
public String dropTablePrefix(String tableName) {
return "";
}
/**
* Return the cascade option for the drop table command.
*/
@@ -1,7 +1,6 @@
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.GlobalProperties;
import javax.sql.DataSource;
@@ -14,14 +13,13 @@ public class H2Platform extends DatabasePlatform {
super();
this.name = "h2";
this.dbEncrypt = new H2DbEncrypt();
// like ? escape'' not working in the latest version H2 so just using no
// escape clause for now noting that backslash is an escape char for like in H2
this.likeClause = "like ?";
// only support getGeneratedKeys with non-batch JDBC
// so generally use SEQUENCE instead of IDENTITY for H2
boolean useIdentity = GlobalProperties.getBoolean("ebean.h2platform.useIdentity", false);
IdType idType = useIdentity ? IdType.IDENTITY : IdType.SEQUENCE;
this.dbIdentity.setIdType(idType);
this.dbIdentity.setIdType(IdType.SEQUENCE);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.dbIdentity.setSupportsIdentity(true);
@@ -1,11 +1,9 @@
package com.avaje.ebean.config.dbplatform;
import java.sql.Types;
import com.avaje.ebean.BackgroundExecutor;
import javax.sql.DataSource;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.GlobalProperties;
import java.sql.Types;
/**
* H2 specific platform.
@@ -16,14 +14,7 @@ public class HsqldbPlatform extends DatabasePlatform {
super();
this.name = "hsqldb";
this.dbEncrypt = new H2DbEncrypt();
// only support getGeneratedKeys with non-batch JDBC
// so generally use SEQUENCE instead of IDENTITY for H2
boolean useIdentity = GlobalProperties.getBoolean("ebean.hsqldb.useIdentity", true);
IdType idType = useIdentity ? IdType.IDENTITY : IdType.SEQUENCE;
this.dbIdentity.setIdType(idType);
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.dbIdentity.setSupportsIdentity(true);
@@ -17,8 +17,12 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
public MsSqlServer2005Platform() {
super();
this.name = "mssqlserver2005";
// effectively disable persistBatchOnCascade mode for SQL Server
// due to lack of support for getGeneratedKeys in batch mode
this.disallowBatchOnCascade = true;
this.idInExpandedForm = true;
this.sqlLimiter = new MsSqlServer2005SqlLimiter();
this.dbDdlSyntax.setIdentity("identity(1,1)");
this.dbDdlSyntax = new MsDdlSyntax();
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsIdentity(true);
@@ -28,6 +32,7 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
dbTypeMap.put(Types.BOOLEAN, new DbType("bit default 0"));
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
dbTypeMap.put(Types.BIGINT, new DbType("numeric", 19));
dbTypeMap.put(Types.REAL, new DbType("float(16)"));
dbTypeMap.put(Types.DOUBLE, new DbType("float(32)"));
@@ -45,4 +50,30 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
}
/**
* MS SQL Server specific DDL Syntax.
*/
public class MsDdlSyntax extends DbDdlSyntax {
MsDdlSyntax() {
this.identity = "identity(1,1)";
this.dropKeyConstraints = true;
}
/**
* Return some DDL to disable constraints on the given table.
*/
public String dropKeyConstraintPrefix(String tableName, String fkName) {
return "IF OBJECT_ID('"+fkName+"', 'F') IS NOT NULL";
}
/**
* Return prefix text that goes before drop table.
*/
public String dropTablePrefix(String tableName) {
return "IF OBJECT_ID('"+tableName+"', 'U') IS NOT NULL ";
}
}
}
@@ -20,6 +20,7 @@ public class MySqlPlatform extends DatabasePlatform {
public MySqlPlatform() {
super();
this.name = "mysql";
this.likeClause = "like ? escape''";
this.selectCountWithAlias = true;
this.dbEncrypt = new MySqlDbEncrypt();
@@ -36,7 +37,7 @@ public class MySqlPlatform extends DatabasePlatform {
dbTypeMap.put(Types.BIT, new DbType("tinyint(1) default 0"));
dbTypeMap.put(Types.BOOLEAN, new DbType("tinyint(1) default 0"));
dbTypeMap.put(Types.TIMESTAMP, new DbType("datetime"));
dbTypeMap.put(Types.TIMESTAMP, new DbType("datetime(6)"));
dbTypeMap.put(Types.CLOB, new MySqlClob());
dbTypeMap.put(Types.BLOB, new MySqlBlob());
dbTypeMap.put(Types.BINARY, new DbType("binary", 255));
@@ -1,7 +1,5 @@
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.config.GlobalProperties;
/**
* Oracle encryption support.
*
@@ -48,12 +46,19 @@ import com.avaje.ebean.config.GlobalProperties;
public class Oracle10DbEncrypt extends AbstractDbEncrypt {
/**
* Constructs the Oracle10DbEncrypt.
* Constructs the Oracle10DbEncrypt with default encrypt and decrypt stored procedures.
*/
public Oracle10DbEncrypt() {
this("eb_encrypt", "eb_decrypt");
}
String encryptfunction = GlobalProperties.get("ebean.oracle.encryptfunction", "eb_encrypt");
String decryptfunction = GlobalProperties.get("ebean.oracle.decryptfunction", "eb_decrypt");
/**
* Constructs the Oracle10DbEncrypt specifying encrypt and decrypt stored procedures.
*
* @param encryptfunction the encrypt stored procedure
* @param decryptfunction the decrypt stored procedure
*/
public Oracle10DbEncrypt(String encryptfunction, String decryptfunction) {
this.varcharEncryptFunction = new OraVarcharFunction(encryptfunction, decryptfunction);
this.dateEncryptFunction = new OraDateFunction(encryptfunction, decryptfunction);
@@ -1,67 +1,68 @@
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.BackgroundExecutor;
import javax.sql.DataSource;
import java.sql.Types;
/**
* Oracle10 and greater specific platform.
*/
public class Oracle10Platform extends DatabasePlatform {
public Oracle10Platform() {
super();
this.name = "oracle";
this.dbEncrypt = new Oracle10DbEncrypt();
this.sqlLimiter = new RownumSqlLimiter();
// Not using getGeneratedKeys as instead we will
// batch load sequences which enables JDBC batch execution
dbIdentity.setSupportsGetGeneratedKeys(false);
dbIdentity.setIdType(IdType.SEQUENCE);
dbIdentity.setSupportsSequence(true);
this.treatEmptyStringsAsNull = true;
this.openQuote = "\"";
this.closeQuote = "\"";
booleanDbType = Types.INTEGER;
dbTypeMap.put(Types.BOOLEAN, new DbType("number(1) default 0"));
dbTypeMap.put(Types.INTEGER, new DbType("number", 10));
dbTypeMap.put(Types.BIGINT, new DbType("number", 19));
dbTypeMap.put(Types.REAL, new DbType("number", 19, 4));
dbTypeMap.put(Types.DOUBLE, new DbType("number", 19, 4));
dbTypeMap.put(Types.SMALLINT, new DbType("number", 5));
dbTypeMap.put(Types.TINYINT, new DbType("number", 3));
dbTypeMap.put(Types.DECIMAL, new DbType("number", 38));
dbTypeMap.put(Types.VARCHAR, new DbType("varchar2", 255));
dbTypeMap.put(Types.LONGVARBINARY, new DbType("blob"));
dbTypeMap.put(Types.LONGVARCHAR, new DbType("clob"));
dbTypeMap.put(Types.VARBINARY, new DbType("raw", 255));
dbTypeMap.put(Types.BINARY, new DbType("raw", 255));
dbTypeMap.put(Types.TIME, new DbType("timestamp"));
dbDdlSyntax.setDropTableCascade("cascade constraints purge");
dbDdlSyntax.setIdentity(null);
dbDdlSyntax.setMaxConstraintNameLength(30);
}
@Override
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
String seqName, int batchSize) {
return new OracleSequenceIdGenerator(be, ds, seqName, batchSize);
}
@Override
protected String withForUpdate(String sql) {
return sql + " for update";
}
}
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.BackgroundExecutor;
import javax.sql.DataSource;
import java.sql.Types;
/**
* Oracle10 and greater specific platform.
*/
public class Oracle10Platform extends DatabasePlatform {
public Oracle10Platform() {
super();
this.name = "oracle";
// OnQueryOnly.CLOSE as a performance optimisation on Oracle
this.onQueryOnly = OnQueryOnly.CLOSE;
this.dbEncrypt = new Oracle10DbEncrypt();
this.sqlLimiter = new RownumSqlLimiter();
// Not using getGeneratedKeys as instead we will
// batch load sequences which enables JDBC batch execution
dbIdentity.setSupportsGetGeneratedKeys(false);
dbIdentity.setIdType(IdType.SEQUENCE);
dbIdentity.setSupportsSequence(true);
this.treatEmptyStringsAsNull = true;
this.openQuote = "\"";
this.closeQuote = "\"";
booleanDbType = Types.INTEGER;
dbTypeMap.put(Types.BOOLEAN, new DbType("number(1) default 0"));
dbTypeMap.put(Types.INTEGER, new DbType("number", 10));
dbTypeMap.put(Types.BIGINT, new DbType("number", 19));
dbTypeMap.put(Types.REAL, new DbType("number", 19, 4));
dbTypeMap.put(Types.DOUBLE, new DbType("number", 19, 4));
dbTypeMap.put(Types.SMALLINT, new DbType("number", 5));
dbTypeMap.put(Types.TINYINT, new DbType("number", 3));
dbTypeMap.put(Types.DECIMAL, new DbType("number", 38));
dbTypeMap.put(Types.VARCHAR, new DbType("varchar2", 255));
dbTypeMap.put(Types.LONGVARBINARY, new DbType("blob"));
dbTypeMap.put(Types.LONGVARCHAR, new DbType("clob"));
dbTypeMap.put(Types.VARBINARY, new DbType("raw", 255));
dbTypeMap.put(Types.BINARY, new DbType("raw", 255));
dbTypeMap.put(Types.TIME, new DbType("timestamp"));
dbDdlSyntax.setDropTableCascade("cascade constraints purge");
dbDdlSyntax.setIdentity(null);
dbDdlSyntax.setMaxConstraintNameLength(30);
}
@Override
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
String seqName, int batchSize) {
return new OracleSequenceIdGenerator(be, ds, seqName, batchSize);
}
@Override
protected String withForUpdate(String sql) {
return sql + " for update";
}
}
@@ -1,7 +1,6 @@
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.GlobalProperties;
import javax.sql.DataSource;
import java.sql.Types;
@@ -27,12 +26,7 @@ public class Postgres8Platform extends DatabasePlatform {
this.dbIdentity.setIdType(IdType.SEQUENCE);
this.dbIdentity.setSupportsSequence(true);
String colAlias = GlobalProperties.get("ebean.columnAliasPrefix", null);
if (colAlias == null) {
// Postgres requires the "as" keyword for column alias
GlobalProperties.put("ebean.columnAliasPrefix", "as c");
}
this.columnAliasPrefix = "as c";
this.openQuote = "\"";
this.closeQuote = "\"";
@@ -1,83 +1,80 @@
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.GlobalProperties;
import javax.sql.DataSource;
import java.sql.Types;
/**
* Postgres v9 specific platform.
* <p>
* Uses serial types and getGeneratedKeys.
* </p>
*/
public class PostgresPlatform extends DatabasePlatform {
/**
* Unique jdbc type id defined for hstore type.
*/
public static final int TYPE_HSTORE = 4001;
public PostgresPlatform() {
super();
this.name = "postgres";
this.dbDdlSyntax = new PostgresDdlSyntax();
this.selectCountWithAlias = true;
this.blobDbType = Types.LONGVARBINARY;
this.clobDbType = Types.VARCHAR;
this.dbEncrypt = new PostgresDbEncrypt();
// Use Identity and getGeneratedKeys
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
String colAlias = GlobalProperties.get("ebean.columnAliasPrefix", null);
if (colAlias == null) {
// Postgres requires the "as" keyword for column alias
GlobalProperties.put("ebean.columnAliasPrefix", "as c");
}
this.openQuote = "\"";
this.closeQuote = "\"";
dbTypeMap.put(TYPE_HSTORE, new DbType("hstore"));
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 38));
dbTypeMap.put(Types.BINARY, new DbType("bytea", false));
dbTypeMap.put(Types.VARBINARY, new DbType("bytea", false));
dbTypeMap.put(Types.BLOB, new DbType("bytea", false));
dbTypeMap.put(Types.CLOB, new DbType("text"));
dbTypeMap.put(Types.LONGVARBINARY, new DbType("bytea", false));
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
dbDdlSyntax.setDropTableCascade("cascade");
dbDdlSyntax.setDropIfExists("if exists");
}
/**
* Create a Postgres specific sequence IdGenerator.
*/
@Override
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
String seqName, int batchSize) {
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
}
@Override
protected String withForUpdate(String sql) {
return sql + " for update";
}
}
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.BackgroundExecutor;
import javax.sql.DataSource;
import java.sql.Types;
/**
* Postgres v9 specific platform.
* <p>
* Uses serial types and getGeneratedKeys.
* </p>
*/
public class PostgresPlatform extends DatabasePlatform {
/**
* Unique jdbc type id defined for hstore type.
*/
public static final int TYPE_HSTORE = 4001;
public PostgresPlatform() {
super();
this.name = "postgres";
// OnQueryOnly.CLOSE as a performance optimisation on Postgres
this.onQueryOnly = OnQueryOnly.CLOSE;
this.likeClause = "like ? escape''";
this.dbDdlSyntax = new PostgresDdlSyntax();
this.selectCountWithAlias = true;
this.blobDbType = Types.LONGVARBINARY;
this.clobDbType = Types.VARCHAR;
this.dbEncrypt = new PostgresDbEncrypt();
// Use Identity and getGeneratedKeys
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.columnAliasPrefix = "as c";
this.openQuote = "\"";
this.closeQuote = "\"";
dbTypeMap.put(TYPE_HSTORE, new DbType("hstore"));
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 38));
dbTypeMap.put(Types.BINARY, new DbType("bytea", false));
dbTypeMap.put(Types.VARBINARY, new DbType("bytea", false));
dbTypeMap.put(Types.BLOB, new DbType("bytea", false));
dbTypeMap.put(Types.CLOB, new DbType("text"));
dbTypeMap.put(Types.LONGVARBINARY, new DbType("bytea", false));
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
dbDdlSyntax.setDropTableCascade("cascade");
dbDdlSyntax.setDropIfExists("if exists");
}
/**
* Create a Postgres specific sequence IdGenerator.
*/
@Override
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
String seqName, int batchSize) {
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
}
@Override
protected String withForUpdate(String sql) {
return sql + " for update";
}
}
@@ -40,7 +40,7 @@ public abstract class SequenceIdGenerator implements IdGenerator {
protected final BackgroundExecutor backgroundExecutor;
protected final ArrayList<Integer> idList = new ArrayList<Integer>(50);
protected final ArrayList<Long> idList = new ArrayList<Long>(50);
protected int batchSize;
@@ -117,7 +117,7 @@ public abstract class SequenceIdGenerator implements IdGenerator {
if (idList.size() == 0) {
loadMoreIds(batchSize, t);
}
Integer nextId = idList.remove(0);
Long nextId = idList.remove(0);
if (batchSize > 1) {
if (idList.size() <= batchSize / 2) {
@@ -158,7 +158,7 @@ public abstract class SequenceIdGenerator implements IdGenerator {
protected void loadMoreIds(final int numberToLoad, Transaction t) {
ArrayList<Integer> newIds = getMoreIds(numberToLoad, t);
ArrayList<Long> newIds = getMoreIds(numberToLoad, t);
if (logger.isDebugEnabled()) {
logger.debug("... seq:" + seqName + " loaded:" + numberToLoad + " ids:" + newIds);
@@ -174,11 +174,11 @@ public abstract class SequenceIdGenerator implements IdGenerator {
/**
* Get more Id's by executing a query and reading the Id's returned.
*/
protected ArrayList<Integer> getMoreIds(int loadSize, Transaction t) {
protected ArrayList<Long> getMoreIds(int loadSize, Transaction t) {
String sql = getSql(loadSize);
ArrayList<Integer> newIds = new ArrayList<Integer>(loadSize);
ArrayList<Long> newIds = new ArrayList<Long>(loadSize);
boolean useTxnConnection = t != null;
@@ -191,12 +191,10 @@ public abstract class SequenceIdGenerator implements IdGenerator {
pstmt = c.prepareStatement(sql);
rset = pstmt.executeQuery();
while (rset.next()) {
int val = rset.getInt(1);
newIds.add(Integer.valueOf(val));
newIds.add(rset.getLong(1));
}
if (newIds.size() == 0) {
String m = "Always expecting more than 1 row from " + sql;
throw new PersistenceException(m);
throw new PersistenceException("Always expecting more than 1 row from " + sql);
}
return newIds;
@@ -0,0 +1,78 @@
package com.avaje.ebean.event;
import java.util.Set;
/**
* Provides a base implementation of BeanPersistListener.
* <p>
* Objects extending this should override the methods then are interested in.
* The default inserted() updated() and deleted() methods return false and as such
* means other servers in the cluster are not notified.
* </p>
*/
public abstract class AbstractBeanPersistListener implements BeanPersistListener {
/**
* Notified that a bean has been inserted locally. Return true if you want the
* cluster to be notified of the event.
*
* @param bean The bean that was inserted.
*/
@Override
public boolean inserted(Object bean) {
return false;
}
/**
* Notified that a bean has been updated locally. Return true if you want the
* cluster to be notified of the event.
*
* @param bean The bean that was updated.
* @param updatedProperties The properties that were modified by this update.
*/
@Override
public boolean updated(Object bean, Set<String> updatedProperties) {
return false;
}
/**
* Notified that a bean has been deleted locally. Return true if you want the
* cluster to be notified of the event.
*
* @param bean The bean that was deleted.
*/
@Override
public boolean deleted(Object bean) {
return false;
}
/**
* Notify that a bean was inserted on another node of the cluster.
*
* @param id the id value of the inserted bean
*/
@Override
public void remoteInsert(Object id) {
// do nothing
}
/**
* Notify that a bean was updated on another node of the cluster.
*
* @param id the id value of the updated bean.
*/
@Override
public void remoteUpdate(Object id) {
// do nothing
}
/**
* Notify that a bean was deleted on another node of the cluster.
*
* @param id the id value of the deleted bean.
*/
@Override
public void remoteDelete(Object id) {
// do nothing
}
}
@@ -33,11 +33,17 @@ import com.avaje.ebean.config.ServerConfig;
* </p>
* <p>
* A BeanPersistListener is either found automatically via class path search or
* can be added programmatically via {@link ServerConfig#add(BeanPersistListener)}.
* can be added programmatically via {@link ServerConfig#add(BeanPersistListener)}}.
* </p>
* @see ServerConfig#add(BeanPersistListener)
*/
public interface BeanPersistListener<T> {
public interface BeanPersistListener {
/**
* Return true if this BeanPersistListener should be registered for events
* on this entity type.
*/
public boolean isRegisterFor(Class<?> cls);
/**
* Notified that a bean has been inserted locally. Return true if you want the
@@ -46,7 +52,7 @@ public interface BeanPersistListener<T> {
* @param bean
* The bean that was inserted.
*/
public boolean inserted(T bean);
public boolean inserted(Object bean);
/**
* Notified that a bean has been updated locally. Return true if you want the
@@ -57,7 +63,7 @@ public interface BeanPersistListener<T> {
* @param updatedProperties
* The properties that were modified by this update.
*/
public boolean updated(T bean, Set<String> updatedProperties);
public boolean updated(Object bean, Set<String> updatedProperties);
/**
* Notified that a bean has been deleted locally. Return true if you want the
@@ -66,7 +72,7 @@ public interface BeanPersistListener<T> {
* @param bean
* The bean that was deleted.
*/
public boolean deleted(T bean);
public boolean deleted(Object bean);
/**
* Notify that a bean was inserted on another node of the cluster.
+15 -15
View File
@@ -3,8 +3,8 @@
<title>Ebean API</title>
</head>
<body BGCOLOR="#ffffff">
Ebean Object Relational Mapping (start at <a href='com/avaje/ebean/Ebean.html'>Ebean</a>
or <a href='com/avaje/ebean/EbeanServer.html'>EbeanServer</a>).
Ebean Object Relational Mapping (start at
<a href='com/avaje/ebean/EbeanServer.html'>EbeanServer</a> or <a href='com/avaje/ebean/Ebean.html'>Ebean</a>).
<h3>Ebean</h3>
@@ -21,15 +21,15 @@ For a full description of the query language refer to <a href="com/avaje/ebean/Q
<h3>
EXAMPLE 1: Simple fetch
</h3>
<pre class="code">
<pre>{@code
// fetch order 10
Order order = Ebean.find(Order.class, 10);
</pre>
}</pre>
<h3>
EXAMPLE 2: Fetch an Object with associations
</h3>
<pre class="code">
<pre>{@code
// fetch Customer 7 including their billing and shipping addresses
Customer customer = Ebean.find(Customer.class)
.fetch("billingAddress");
@@ -40,19 +40,19 @@ Customer customer = Ebean.find(Customer.class)
Address billAddr = customer.getBillingAddress();
Address shipAddr = customer.getShippingAddress();
</pre>
}</pre>
<h3>
EXAMPLE 3: Fetch a list of Objects with associations
</h3>
<pre class="code">
<pre>{@code
// Note: This example shows a "Partial Object".
// For the product objects associated with the
// order details only the product id and name is
// fetched (the product objects are partially populated).
// fetch orders for customer.id = 2
List&lt;Order&gt; orderList = Ebean.find(Order.class);
List<Order> orderList = Ebean.find(Order.class);
.fetch("customer")
.fetch("customer.shippingAddress")
.fetch("details")
@@ -72,17 +72,17 @@ Order order = orderList.get(0);
Customer customer = order.getCustomer();
Address shipAddr = customer.getShippingAddress();
List&lt;OrderDetail&gt; details = order.getDetails();
List<OrderDetail> details = order.getDetails();
OrderDetail detail = details.get(0);
Product product = detail.getProduct();
String productName = product.getName();
</pre>
}</pre>
<h3>
EXAMPLE 4: Create and save an Order
</h3>
<pre class="code">
<pre>{@code
// get a Customer reference so we don't hit the database
Customer custRef = Ebean.getReference(Customer.class, 7);
@@ -107,14 +107,14 @@ orderLines.add(line);
// NB: assumes CascadeType.PERSIST is set on the order lines association
Ebean.save(newOrder);
</pre>
}</pre>
<h3>
EXAMPLE 5: Use another database
</h3>
<pre class="code">
<pre>{@code
// Get access to the Human Resources EbeanServer/Database
EbeanServer hrServer = Ebean.getServer(&quot;HR&quot;);
EbeanServer hrServer = Ebean.getServer("HR");
// fetch contact 3 from the HR database
@@ -125,7 +125,7 @@ contact.setStatus(Contact.Status.INACTIVE);
// save the contact back to the HR database
hrServer.save(contact);
</pre>
}</pre>
</div>
@@ -19,9 +19,6 @@ import com.avaje.ebean.Query;
* properties and applying that to both what to fetch (ORM query) and what to
* render (JAX-RS JSON / XML).
* </p>
*
* @author rbygrave
*
*/
public class PathProperties {
@@ -53,6 +53,9 @@ class PathPropertiesParser {
case '(':
return currentWord();
default:
if (pos == 1) {
return "";
}
}
} while (pos < eof);
throw new RuntimeException("Hit EOF while reading sectionTitle from " + startPos);
@@ -91,6 +94,10 @@ class PathPropertiesParser {
}
} while (pos < eof);
if (startPos < pos) {
String currentWord = source.substring(startPos, pos);
currentPathProps.addProperty(currentWord);
}
}
private void addSubpath() {
@@ -1,7 +1,7 @@
package com.avaje.ebean.text;
/**
* An exception occured typically in processing CSV, JSON or XML.
* An exception occurred typically in processing CSV, JSON or XML.
*
* @author rbygrave
*/
@@ -0,0 +1,115 @@
package com.avaje.ebean.text.json;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
/**
* Utility that converts between JSON content and simple java Maps/Lists.
*/
public class EJson {
/**
* Write the nested Map/List as json.
*/
public static String write(Object object) throws IOException {
return EJsonWriter.write(object);
}
/**
* Write the nested Map/List as json to the writer.
*/
public static void write(Object object, Writer writer) throws IOException {
EJsonWriter.write(object, writer);
}
/**
* Write the nested Map/List as json to the jsonGenerator.
*/
public static void write(Object object, JsonGenerator jsonGenerator) throws IOException {
EJsonWriter.write(object, jsonGenerator);
}
/**
* Parse the json and return as a Map.
*/
public static Map<String,Object> parseObject(String json) throws IOException {
return EJsonReader.parseObject(json);
}
/**
* Parse the json and return as a Map taking a reader.
*/
public static Map<String,Object> parseObject(Reader reader) throws IOException {
return EJsonReader.parseObject(reader);
}
/**
* Parse the json and return as a Map taking a JsonParser.
*/
public static Map<String,Object> parseObject(JsonParser parser) throws IOException {
return EJsonReader.parseObject(parser);
}
/**
* Parse the json and return as a Map taking a JsonParser and a starting token.
* <p>
* Used when the first token is checked to see if the value is null prior to calling this.
* </p>
*/
public static Map<String,Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
return EJsonReader.parseObject(parser, token);
}
/**
* Parse the json and return as a List.
* @throws IOException
*/
public static List<Object> parseList(String json) throws IOException {
return EJsonReader.parseList(json);
}
/**
* Parse the json and return as a List taking a Reader.
* @throws IOException
*/
public static List<Object> parseList(Reader reader) throws IOException {
return EJsonReader.parseList(reader);
}
/**
* Parse the json and return as a List taking a JsonParser.
*/
public static List<Object> parseList(JsonParser parser) throws IOException {
return EJsonReader.parseList(parser);
}
/**
* Parse the json and return as a List or Map.
*/
public static Object parse(String json) throws IOException {
return EJsonReader.parse(json);
}
/**
* Parse the json and return as a List or Map.
*/
public static Object parse(Reader reader) throws IOException {
return EJsonReader.parse(reader);
}
/**
* Parse the json and return as a List or Map.
*/
public static Object parse(JsonParser parser) throws IOException {
return EJsonReader.parse(parser);
}
}
@@ -0,0 +1,287 @@
package com.avaje.ebean.text.json;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
class EJsonReader {
static JsonFactory json = new JsonFactory();
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(String json) throws IOException {
return (Map<String, Object>) parse(json);
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(Reader reader) throws IOException {
return (Map<String, Object>) parse(reader);
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(JsonParser parser) throws IOException {
return (Map<String, Object>) parse(parser);
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
return (Map<String, Object>)parse(parser, token);
}
@SuppressWarnings("unchecked")
static List<Object> parseList(String json) throws IOException {
return (List<Object>) parse(json);
}
@SuppressWarnings("unchecked")
static List<Object> parseList(Reader reader) throws IOException {
return (List<Object>) parse(reader);
}
@SuppressWarnings("unchecked")
static List<Object> parseList(JsonParser parser) throws IOException {
return (List<Object>) parse(parser);
}
static Object parse(String json) throws IOException {
return parse(new StringReader(json));
}
static Object parse(Reader reader) throws IOException {
return parse(json.createParser(reader));
}
static Object parse(JsonParser parser) throws IOException {
return parse(parser, null);
}
static Object parse(JsonParser parser, JsonToken token) throws IOException {
return new EJsonReader(parser).parseJson(token);
}
private final JsonParser parser;
private int depth;
private Stack stack;
private Context currentContext;
EJsonReader(JsonParser parser) {
this.parser = parser;
}
private void startArray() {
depth++;
stack.push(currentContext);
currentContext = new ArrayContext();
}
private void startObject() {
depth++;
stack.push(currentContext);
currentContext = new ObjectContext();
}
private void endArray() {
end();
}
private void endObject() {
end();
}
private void end() {
depth--;
if (!stack.isEmpty()) {
currentContext = stack.pop(currentContext);
}
}
private void setValue(Object value) {
currentContext.setValue(value);
}
private void setValueNull() {
currentContext.setValueNull();
}
private Object parseJson(JsonToken token) throws IOException {
if (token == null) {
token = parser.nextToken();
// if it is a simple value just return it
switch (token) {
case VALUE_NULL: return null;
case VALUE_FALSE: return Boolean.FALSE;
case VALUE_TRUE: return Boolean.TRUE;
case VALUE_STRING: return parser.getText();
case VALUE_NUMBER_INT: return parser.getLongValue();
case VALUE_NUMBER_FLOAT: return parser.getDecimalValue();
}
}
// it is a object or array, process the first JsonToken
stack = new Stack();
processJsonToken(token);
// process the rest of the object or array
while (depth > 0) {
token = parser.nextToken();
processJsonToken(token);
}
return currentContext.getValue();
}
/**
* Process the JsonToken for objects and arrays.
*/
private void processJsonToken(JsonToken token) throws IOException {
switch (token) {
case START_ARRAY:
startArray();
break;
case START_OBJECT:
startObject();
break;
case FIELD_NAME:
currentContext.setKey(parser.getCurrentName());
break;
case VALUE_STRING:
setValue(parser.getValueAsString());
break;
case VALUE_NUMBER_INT:
setValue(parser.getLongValue());
break;
case VALUE_NUMBER_FLOAT:
setValue(parser.getDecimalValue());
break;
case VALUE_TRUE:
setValue(Boolean.TRUE);
break;
case VALUE_FALSE:
setValue(Boolean.FALSE);
break;
case VALUE_NULL:
setValueNull();
break;
case END_OBJECT:
endObject();
break;
case END_ARRAY:
endArray();
break;
default:
break;
}
}
private static final class Stack {
private Context head;
private void push(Context context) {
if (context != null) {
context.next = head;
head = context;
}
}
private Context pop(Context endingContext) {
if (head == null) {
throw new NoSuchElementException();
}
Context temp = head;
head = head.next;
temp.popContext(endingContext);
return temp;
}
private boolean isEmpty() {
return head == null;
}
}
private static abstract class Context {
Context next;
abstract void popContext(Context temp);
abstract Object getValue();
abstract void setKey(String key);
abstract void setValue(Object value);
abstract void setValueNull();
}
private static class ObjectContext extends Context {
private final Map<String, Object> map = new LinkedHashMap<String, Object>();
private String key;
public void popContext(Context temp) {
setValue(temp.getValue());
}
Object getValue() {
return map;
}
void setKey(String key) {
this.key = key;
}
void setValue(Object value) {
map.put(key, value);
}
void setValueNull() {
map.put(key, null);
}
}
private static class ArrayContext extends Context {
private final List<Object> values = new ArrayList<Object>();
public void popContext(Context temp) {
values.add(temp.getValue());
}
Object getValue() {
return values;
}
void setValue(Object value) {
values.add(value);
}
void setValueNull() {
// ignore
}
void setKey(String key) {
// not expected
}
}
}
@@ -0,0 +1,209 @@
package com.avaje.ebean.text.json;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
class EJsonWriter {
/**
* Base jsonFactory implementation used when it is not passed in.
*/
static JsonFactory jsonFactory = new JsonFactory();
static String write(Object object) throws IOException {
StringWriter writer = new StringWriter(200);
write(object, writer);
return writer.toString();
}
static void write(Object object, Writer writer) throws IOException {
JsonGenerator generator = jsonFactory.createGenerator(writer);
write(object, generator);
generator.close();
}
static void write(Object object, JsonGenerator jsonGenerator) {
new EJsonWriter(jsonGenerator).writeJson(object);
}
private final JsonGenerator jsonGenerator;
private EJsonWriter(JsonGenerator jsonGenerator) {
this.jsonGenerator = jsonGenerator;
}
private void writeJson(Object object) {
writeJson(null, object);
}
@SuppressWarnings("unchecked")
private void writeJson(String name, Object object) {
try {
if (object == null) {
writeNull(name);
} else if (object instanceof Map) {
writeMap(name, (Map<Object, Object>) object);
} else if (object instanceof Collection) {
writeCollection(name, (Collection<Object>) object);
} else if (object instanceof Boolean) {
writeBoolean(name, (Boolean) object);
} else if (object instanceof Number) {
writeNumber(name, (Number) object);
} else if (object instanceof Date) {
writeDate(name, (Date) object);
} else if (object instanceof String) {
writeString(name, (String) object);
} else if (object instanceof Map.Entry<?, ?>) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object;
writeJson(entry.getKey().toString(), entry.getValue());
} else {
writeString(name, object.toString());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void writeBoolean(String name, Boolean object) throws IOException {
if (name == null) {
jsonGenerator.writeBoolean(object);
} else {
jsonGenerator.writeBooleanField(name, object);
}
}
private void writeDate(String name, Date object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber(object.getTime());
} else {
jsonGenerator.writeNumberField(name, object.getTime());
}
}
private void writeNumber(String name, Number object) throws IOException {
if (object instanceof Long) {
writeLong(name, object);
} else if (object instanceof Integer) {
writeInteger(name, object);
} else if (object instanceof Double) {
writeDouble(name, object);
} else if (object instanceof BigDecimal) {
writeBigDecimal(name, object);
} else if (object instanceof BigInteger) {
writeBigInteger(name, object);
} else {
writeGeneralNumber(name, object);
}
}
private void writeGeneralNumber(String name, Number object) throws IOException {
writeBigDecimal(name, new BigDecimal(object.toString()));
}
private void writeBigDecimal(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((BigDecimal) object);
} else {
jsonGenerator.writeNumberField(name, (BigDecimal) object);
}
}
private void writeBigInteger(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((BigInteger) object);
} else {
jsonGenerator.writeNumberField(name, object.longValue());
}
}
private void writeDouble(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((Double) object);
} else {
jsonGenerator.writeNumberField(name, (Double) object);
}
}
private void writeLong(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((Long) object);
} else {
jsonGenerator.writeNumberField(name, (Long) object);
}
}
private void writeInteger(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((Integer) object);
} else {
jsonGenerator.writeNumberField(name, (Integer) object);
}
}
private void writeNull(String name) throws IOException {
if (name == null) {
jsonGenerator.writeNull();
} else {
jsonGenerator.writeNullField(name);
}
}
private void writeString(String name, String object) throws IOException {
if (name == null) {
jsonGenerator.writeString(object);
} else {
jsonGenerator.writeStringField(name, object);
}
}
private void writeCollection(String name, Collection<Object> collection) throws IOException {
if (name != null) {
jsonGenerator.writeFieldName(name);
}
jsonGenerator.writeStartArray();
for (Object object : collection) {
writeJson(null, object);
}
jsonGenerator.writeEndArray();
}
private void writeMap(String name, Map<Object, Object> map) throws IOException {
if (name != null) {
jsonGenerator.writeFieldName(name);
}
jsonGenerator.writeStartObject();
Set<Entry<Object, Object>> entrySet = map.entrySet();
for (Entry<Object, Object> entry : entrySet) {
writeJson(entry.getKey().toString(), entry.getValue());
}
jsonGenerator.writeEndObject();
}
}
@@ -1,5 +1,9 @@
package com.avaje.ebean.text.json;
import com.avaje.ebean.text.PathProperties;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
@@ -7,125 +11,156 @@ import java.util.List;
/**
* Converts objects to and from JSON format.
*
* @author rbygrave
*/
public interface JsonContext {
/**
* Convert json string input into a Bean of a specific type.
*
* @throws JsonIOException When IOException occurs
*/
public <T> T toBean(Class<T> rootType, String json);
public <T> T toBean(Class<T> rootType, String json) throws JsonIOException;
/**
* Convert json reader input into a Bean of a specific type.
*
* @throws JsonIOException When IOException occurs
*/
public <T> T toBean(Class<T> rootType, Reader json);
public <T> T toBean(Class<T> rootType, Reader json) throws JsonIOException;
/**
* Convert json string input into a Bean of a specific type with options.
* Convert json parser input into a Bean of a specific type.
*
* @throws JsonIOException When IOException occurs
*/
public <T> T toBean(Class<T> rootType, String json, JsonReadOptions options);
/**
* Convert json reader input into a Bean of a specific type with options.
*/
public <T> T toBean(Class<T> rootType, Reader json, JsonReadOptions options);
public <T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException;
/**
* Convert json string input into a list of beans of a specific type.
*
* @throws JsonIOException When IOException occurs
*/
public <T> List<T> toList(Class<T> rootType, String json);
/**
* Convert json string input into a list of beans of a specific type with
* options.
*/
public <T> List<T> toList(Class<T> rootType, String json, JsonReadOptions options);
public <T> List<T> toList(Class<T> rootType, String json) throws JsonIOException;
/**
* Convert json reader input into a list of beans of a specific type.
*
* @throws JsonIOException When IOException occurs
*/
public <T> List<T> toList(Class<T> rootType, Reader json);
public <T> List<T> toList(Class<T> rootType, Reader json) throws JsonIOException;
/**
* Convert json reader input into a list of beans of a specific type with
* options.
* Convert json parser input into a list of beans of a specific type.
*
* @throws JsonIOException When IOException occurs
*/
public <T> List<T> toList(Class<T> rootType, Reader json, JsonReadOptions options);
public <T> List<T> toList(Class<T> cls, JsonParser src) throws JsonIOException;
/**
* Use the genericType to determine if this should be converted into a List or
* bean.
*
* @throws JsonIOException When IOException occurs
*/
public Object toObject(Type genericType, Reader json, JsonReadOptions options);
public Object toObject(Type genericType, Reader json) throws JsonIOException;
/**
* Use the genericType to determine if this should be converted into a List or
* bean.
*
* @throws JsonIOException When IOException occurs
*/
public Object toObject(Type genericType, String json, JsonReadOptions options);
public Object toObject(Type genericType, String json) throws JsonIOException;
/**
* Write the bean or collection in JSON format to the writer with default
* options.
*
* @param o
* the bean or collection of beans to write
* @param writer
* used to write the json output to
* Use the genericType to determine if this should be converted into a List or
* bean.
*
* @throws JsonIOException When IOException occurs
*/
public void toJsonWriter(Object o, Writer writer);
public Object toObject(Type genericType, JsonParser jsonParser) throws JsonIOException;
/**
* With additional pretty output option.
* Return the bean or collection as JSON string.
*
* @throws JsonIOException When IOException occurs
*/
public void toJsonWriter(Object o, Writer writer, boolean pretty);
public String toJson(Object value) throws JsonIOException;
/**
* With additional options to specify JsonValueAdapter and
* JsonWriteBeanVisitor's.
*
* @param o
* the bean or collection of beans to write
* @param writer
* used to write the json output to
* @param options
* additional options to control the JSON output
* Write the bean or collection in JSON format to the writer.
*
* @throws JsonIOException When IOException occurs
*/
public void toJsonWriter(Object o, Writer writer, boolean pretty, JsonWriteOptions options);
public void toJson(Object value, Writer writer) throws JsonIOException;
/**
* With additional JSONP callback function.
* Write the bean or collection to the JsonGenerator.
*
* @throws JsonIOException When IOException occurs
*/
public void toJsonWriter(Object o, Writer writer, boolean pretty, JsonWriteOptions options,
String callback);
public void toJson(Object value, JsonGenerator generator) throws JsonIOException;
/**
* Convert a bean or collection to json string using default options.
* Return the bean or collection as JSON string using PathProperties.
*
* @throws JsonIOException When IOException occurs
*/
public String toJsonString(Object o);
public String toJson(Object value, PathProperties pathProperties) throws JsonIOException;
/**
* Convert a bean or collection to json string with pretty format using
* default options.
* Write the bean or collection as json to the writer using the PathProperties.
*/
public String toJsonString(Object o, boolean pretty);
public void toJson(Object value, Writer writer, PathProperties pathProperties) throws JsonIOException;
/**
* Convert a bean or collection to json string using options.
* Write the bean or collection to the JsonGenerator using the PathProperties.
*/
public String toJsonString(Object o, boolean pretty, JsonWriteOptions options);
public void toJson(Object value, JsonGenerator generator, PathProperties pathProperties) throws JsonIOException;
/**
* Convert a bean or collection to json string using a JSONP callback.
* Deprecated in favour of using PathProperties by itself.
* Write json to the JsonGenerator using the JsonWriteOptions.
*
* @deprecated
*/
public String toJsonString(Object o, boolean pretty, JsonWriteOptions options, String callback);
public void toJson(Object value, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException;
/**
* Return true if the type is known as an Entity or Xml type or a List Set or
* Map of known bean types.
* Deprecated in favour of using PathProperties by itself.
* With additional options.
*
* @throws JsonIOException When IOException occurs
* @deprecated
*/
public void toJson(Object value, Writer writer, JsonWriteOptions options) throws JsonIOException;
/**
* Deprecated in favour of using PathProperties by itself.
* Convert a bean or collection to json string.
*
* @throws JsonIOException When IOException occurs
* @deprecated
*/
public String toJson(Object value, JsonWriteOptions options) throws JsonIOException;
/**
* Return true if the type is known as an Entity bean or a List Set or
* Map of entity beans.
*/
public boolean isSupportedType(Type genericType);
/**
* Create and return a new JsonGenerator for the given writer.
*
* @throws JsonIOException When IOException occurs
*/
public JsonGenerator createGenerator(Writer writer) throws JsonIOException;
/**
* Create and return a new JsonParser for the given reader.
*
* @throws JsonIOException When IOException occurs
*/
public JsonParser createParser(Reader reader) throws JsonIOException;
}
@@ -1,39 +0,0 @@
package com.avaje.ebean.text.json;
/**
* Marker interface for all the Raw JSON types.
* <p>
* You will only use the JsonElements when you register a JsonReadBeanVisitor.
* The JSON elements that are not mapped to a bean property are made available
* to the JsonReadBeanVisitor.
* </p>
*
* @see JsonReadBeanVisitor
*
* @author rbygrave
*/
public interface JsonElement {
/**
* Return true if this is a JSON primitive type (null, boolean, number or
* string).
*/
public boolean isPrimitive();
/**
* Return the string value of this primitive JSON element.
* <p>
* This can not be used for JsonElementObject or JsonElementArray.
* </p>
*/
public String toPrimitiveString();
public Object eval(String exp);
public int evalInt(String exp);
public String evalString(String exp);
public boolean evalBoolean(String exp);
}
@@ -1,112 +0,0 @@
package com.avaje.ebean.text.json;
import java.util.ArrayList;
import java.util.List;
/**
* JSON Array element.
* <p>
* You will only use the JsonElements when you register a JsonReadBeanVisitor.
* The JSON elements that are not mapped to a bean property are made available
* to the JsonReadBeanVisitor.
* </p>
*
* @see JsonReadBeanVisitor
*
* @author rbygrave
*/
public class JsonElementArray implements JsonElement {
private final List<JsonElement> values = new ArrayList<JsonElement>();
public List<JsonElement> getValues() {
return values;
}
public void add(JsonElement value) {
values.add(value);
}
public String toString() {
return values.toString();
}
public boolean isPrimitive() {
return false;
}
public String toPrimitiveString() {
return null;
}
private String[] split(String exp) {
int pos = exp.indexOf('.');
if (pos == -1) {
return new String[] { exp, null };
}
String exp0 = exp.substring(0, pos);
String exp1 = exp.substring(pos + 1);
return new String[] { exp0, exp1 };
}
public Object eval(String exp) {
String[] e = split(exp);
return eval(e[0], e[1]);
}
public int evalInt(String exp) {
String[] e = split(exp);
return evalInt(e[0], e[1]);
}
public String evalString(String exp) {
String[] e = split(exp);
return evalString(e[0], e[1]);
}
public boolean evalBoolean(String exp) {
// TODO Auto-generated method stub
return false;
}
private Object eval(String exp0, String exp1) {
if ("size".equals(exp0)) {
return values.size();
}
if ("isEmpty".equals(exp0)) {
return values.isEmpty();
}
int idx = Integer.parseInt(exp0);
JsonElement element = values.get(idx);
return element.eval(exp1);
}
private int evalInt(String exp0, String exp1) {
if ("size".equals(exp0)) {
return values.size();
}
if ("isEmpty".equals(exp0)) {
return values.isEmpty() ? 1 : 0;
}
int idx = Integer.parseInt(exp0);
JsonElement element = values.get(idx);
return element.evalInt(exp1);
}
private String evalString(String exp0, String exp1) {
if ("size".equals(exp0)) {
return String.valueOf(values.size());
}
if ("isEmpty".equals(exp0)) {
return String.valueOf(values.isEmpty());
}
int idx = Integer.parseInt(exp0);
JsonElement element = values.get(idx);
return element.evalString(exp1);
}
public String getString() {
return toString();
}
}
@@ -1,63 +0,0 @@
package com.avaje.ebean.text.json;
/**
* JSON boolean element.
* <p>
* You will only use the JsonElements when you register a JsonReadBeanVisitor.
* The JSON elements that are not mapped to a bean property are made available
* to the JsonReadBeanVisitor.
* </p>
*
* @see JsonReadBeanVisitor
*
* @author rbygrave
*/
public class JsonElementBoolean implements JsonElement {
public static final JsonElementBoolean TRUE = new JsonElementBoolean(true);
public static final JsonElementBoolean FALSE = new JsonElementBoolean(false);
private final Boolean value;
private JsonElementBoolean(Boolean value) {
this.value = value;
}
public Boolean getValue() {
return value;
}
public String toString() {
return Boolean.toString(value);
}
public boolean isPrimitive() {
return true;
}
public String toPrimitiveString() {
return value.toString();
}
public Object eval(String exp) {
if (exp != null) {
throw new IllegalArgumentException("expression [" + exp + "] not allowed on boolean");
}
return value;
}
public int evalInt(String exp) {
return value ? 1 : 0;
}
public String evalString(String exp) {
return toString();
}
public boolean evalBoolean(String exp) {
return value;
}
}
@@ -1,57 +0,0 @@
package com.avaje.ebean.text.json;
/**
* JSON null element.
* <p>
* You will only use the JsonElements when you register a JsonReadBeanVisitor.
* The JSON elements that are not mapped to a bean property are made available
* to the JsonReadBeanVisitor.
* </p>
*
* @see JsonReadBeanVisitor
*
* @author rbygrave
*/
public class JsonElementNull implements JsonElement {
public static final JsonElementNull NULL = new JsonElementNull();
private JsonElementNull() {
}
public String getValue() {
return "null";
}
public String toString() {
return "json null";
}
public boolean isPrimitive() {
return true;
}
public String toPrimitiveString() {
return null;
}
public Object eval(String exp) {
if (exp != null) {
throw new IllegalArgumentException("expression [" + exp + "] not allowed on null");
}
return null;
}
public int evalInt(String exp) {
return 0;
}
public String evalString(String exp) {
return null;
}
public boolean evalBoolean(String exp) {
return false;
}
}
@@ -1,67 +0,0 @@
package com.avaje.ebean.text.json;
/**
* JSON number element.
* <p>
* You will only use the JsonElements when you register a JsonReadBeanVisitor.
* The JSON elements that are not mapped to a bean property are made available
* to the JsonReadBeanVisitor.
* </p>
*
* @see JsonReadBeanVisitor
*
* @author rbygrave
*/
public class JsonElementNumber implements JsonElement {
private final String value;
public JsonElementNumber(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public String toString() {
return value;
}
public boolean isPrimitive() {
return true;
}
public String toPrimitiveString() {
return value;
}
public Object eval(String exp) {
if (exp != null) {
throw new IllegalArgumentException("expression [" + exp + "] not allowed on number");
}
return Double.parseDouble(value);
}
public int evalInt(String exp) {
if (exp != null) {
throw new IllegalArgumentException("expression [" + exp + "] not allowed on number");
}
return Integer.parseInt(value);
}
public String evalString(String exp) {
if (exp != null) {
throw new IllegalArgumentException("expression [" + exp + "] not allowed on number");
}
return value;
}
public boolean evalBoolean(String exp) {
if (exp != null) {
throw new IllegalArgumentException("expression [" + exp + "] not allowed on number");
}
return Boolean.parseBoolean(value);
}
}
@@ -1,108 +0,0 @@
package com.avaje.ebean.text.json;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* JSON Object element.
* <p>
* You will only use the JsonElements when you register a JsonReadBeanVisitor.
* The JSON elements that are not mapped to a bean property are made available
* to the JsonReadBeanVisitor.
* </p>
*
* @see JsonReadBeanVisitor
*
* @author rbygrave
*/
public class JsonElementObject implements JsonElement {
private final Map<String, JsonElement> map = new LinkedHashMap<String, JsonElement>();
public void put(String key, JsonElement value) {
map.put(key, value);
}
private String[] split(String exp) {
int pos = exp.indexOf('.');
if (pos == -1) {
return new String[] { exp, null };
}
String exp0 = exp.substring(0, pos);
String exp1 = exp.substring(pos + 1);
return new String[] { exp0, exp1 };
}
public Object eval(String exp) {
String[] e = split(exp);
return eval(e[0], e[1]);
}
public int evalInt(String exp) {
String[] e = split(exp);
return evalInt(e[0], e[1]);
}
public String evalString(String exp) {
if (exp == null) {
return map.toString();
}
String[] e = split(exp);
return evalString(e[0], e[1]);
}
public boolean evalBoolean(String exp) {
String[] e = split(exp);
return evalBoolean(e[0], e[1]);
}
private Object eval(String exp0, String exp1) {
JsonElement e = map.get(exp0);
return e == null ? null : e.eval(exp1);
}
private int evalInt(String exp0, String exp1) {
JsonElement e = map.get(exp0);
return e == null ? 0 : e.evalInt(exp1);
}
private String evalString(String exp0, String exp1) {
JsonElement e = map.get(exp0);
return e == null ? "" : e.evalString(exp1);
}
private boolean evalBoolean(String exp0, String exp1) {
JsonElement e = map.get(exp0);
return e == null ? false : e.evalBoolean(exp1);
}
public JsonElement get(String key) {
return map.get(key);
}
public JsonElement getValue(String key) {
return map.get(key);
}
public Set<String> keySet() {
return map.keySet();
}
public Set<Map.Entry<String, JsonElement>> entrySet() {
return map.entrySet();
}
public String toString() {
return map.toString();
}
public boolean isPrimitive() {
return false;
}
public String toPrimitiveString() {
return null;
}
}
@@ -1,71 +0,0 @@
package com.avaje.ebean.text.json;
/**
* JSON string element.
* <p>
* You will only use the JsonElements when you register a JsonReadBeanVisitor.
* The JSON elements that are not mapped to a bean property are made available
* to the JsonReadBeanVisitor.
* </p>
*
* @see JsonReadBeanVisitor
*
* @author rbygrave
*/
public class JsonElementString implements JsonElement {
private final String value;
public JsonElementString(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public String toString() {
return value;
}
public boolean isPrimitive() {
return true;
}
public String toPrimitiveString() {
return value;
}
public Object eval(String exp) {
if (exp != null) {
throw new IllegalArgumentException("expression [" + exp + "] not allowed on number");
}
return value;
}
public int evalInt(String exp) {
if (exp != null) {
throw new IllegalArgumentException("expression [" + exp + "] not allowed on number");
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return 0;
}
}
public String evalString(String exp) {
if (exp != null) {
throw new IllegalArgumentException("expression [" + exp + "] not allowed on number");
}
return value;
}
public boolean evalBoolean(String exp) {
if (exp != null) {
throw new IllegalArgumentException("expression [" + exp + "] not allowed on number");
}
return Boolean.parseBoolean(exp);
}
}
@@ -0,0 +1,24 @@
package com.avaje.ebean.text.json;
/**
* Unchecked exception thrown when an IOException occurs in json processing.
* <p>
* Typically wraps the checked IOException.
* </p>
*/
public class JsonIOException extends RuntimeException {
/**
* Construct with an underlying cause.
*/
public JsonIOException(Throwable cause) {
super(cause);
}
/**
* Construct with a message.
*/
public JsonIOException(String message) {
super(message);
}
}
@@ -1,34 +0,0 @@
package com.avaje.ebean.text.json;
import java.util.Map;
/**
* Provides for some custom handling of json content as it is read.
* <p>
* This visit method is called after all the known properties of the bean have
* been processed. Any JSON elements that could not be mapped to known bean
* properties are available in the unmapped Map.
* </p>
*
* @author rbygrave
*
* @param <T>
* The type of entity bean
*/
public interface JsonReadBeanVisitor<T> {
/**
* Visit the bean that has just been processed.
* <p>
* This provides a method of customising the bean and processing any custom
* JSON content.
* </p>
*
* @param bean
* the bean being processed
* @param unmapped
* Map of any JSON elements that didn't map to known bean properties
*/
public void visit(T bean, Map<String, JsonElement> unmapped);
}
@@ -1,71 +0,0 @@
package com.avaje.ebean.text.json;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Provides the ability to customise the reading of JSON content.
* <p>
* You can optionally provide a custom JsonValueAdapter to handle specific
* formatting for Date and DateTime types.
* </p>
* <p>
* You can optionally register JsonReadBeanVisitors to customise the processing
* of the beans as they are processed and handle any custom JSON elements that
* could not be mapped to bean properties.
* </p>
*
* @author rbygrave
*
*/
public class JsonReadOptions {
protected JsonValueAdapter valueAdapter;
protected Map<String, JsonReadBeanVisitor<?>> visitorMap;
/**
* Default constructor.
*/
public JsonReadOptions() {
this.visitorMap = new LinkedHashMap<String, JsonReadBeanVisitor<?>>();
}
/**
* Return the JsonValueAdapter.
*/
public JsonValueAdapter getValueAdapter() {
return valueAdapter;
}
/**
* Return the map of JsonReadBeanVisitor's.
*/
public Map<String, JsonReadBeanVisitor<?>> getVisitorMap() {
return visitorMap;
}
/**
* Set a JsonValueAdapter for custom DateTime and Date formatting.
*/
public JsonReadOptions setValueAdapter(JsonValueAdapter valueAdapter) {
this.valueAdapter = valueAdapter;
return this;
}
/**
* Register a JsonReadBeanVisitor for the root level.
*/
public JsonReadOptions addRootVisitor(JsonReadBeanVisitor<?> visitor) {
return addVisitor(null, visitor);
}
/**
* Register a JsonReadBeanVisitor for a given path.
*/
public JsonReadOptions addVisitor(String path, JsonReadBeanVisitor<?> visitor) {
visitorMap.put(path, visitor);
return this;
}
}
@@ -1,61 +0,0 @@
/**
* Copyright (C) 2009 Authors
*
* This file is part of Ebean.
*
* Ebean is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Ebean is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Ebean; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.avaje.ebean.text.json;
import java.sql.Timestamp;
/**
* Allows you to customise the Date and Timestamp formats.
* <p>
* There is not a standard JSON format for Date or Timestamp types. By default
* Ebean uses ISO8601 "yyyy-MM-dd'T'HH:mm:ss.SSSZ" and "yyyy-MM-dd".
* </p>
* <p>
* Note that Ebean will convert Joda types to either of the Date or Timestamp
* types and back for you.
* </p>
*
* @see JsonReadOptions
*
* @author rbygrave
*/
public interface JsonValueAdapter {
/**
* Convert the Date to json string.
*/
public String jsonFromDate(java.sql.Date date);
/**
* Convert the DateTime to json string.
*/
public String jsonFromTimestamp(java.sql.Timestamp date);
/**
* Parse the JSON string into a Date.
*/
public java.sql.Date jsonToDate(String jsonDate);
/**
* Parse the JSON DateTime into a Timestamp.
*/
public Timestamp jsonToTimestamp(String jsonDateTime);
}
@@ -1,33 +0,0 @@
package com.avaje.ebean.text.json;
/**
* Allows for customising the JSON write processing.
* <p>
* You can use this to add raw JSON content via {@link JsonWriter}.
* </p>
* <p>
* You register a JsonWriteBeanVisitor with {@link JsonWriteOptions}.
* </p>
*
* @author rbygrave
*
* @param <T>
* the type of entity bean
*
* @see JsonWriteOptions
*/
public interface JsonWriteBeanVisitor<T> {
/**
* Visit the bean that has just been writing it's content to JSON. You can
* write your own additional JSON content to the JsonWriter if you wish.
*
* @param bean
* the bean that has been writing it's content
* @param jsonWriter
* the JsonWriter which you can append custom json content to if you
* wish.
*/
public void visit(T bean, JsonWriter jsonWriter);
}
@@ -1,225 +1,42 @@
package com.avaje.ebean.text.json;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import com.avaje.ebean.text.PathProperties;
/**
* Deprecated in favour of just using PathProperties.
*
* Provides options for customising the JSON write process.
* <p>
* You can optionally provide a custom JsonValueAdapter to handle specific
* formatting for Date and DateTime types.
* </p>
* <p>
* You can optionally register JsonWriteBeanVisitors to customise the processing
* of the beans as they are processed and <strong>add raw JSON
* elements</strong>.
* </p>
* <p>
* You can explicitly state which properties to include in the JSON output for
* the root level and each path.
* </p>
*
* <pre class="code">
* // find some customers ...
*
* List&lt;Customer&gt; list = Ebean.find(Customer.class).select(&quot;id, name, status, shippingAddress&quot;)
* .fetch(&quot;billingAddress&quot;,
* &quot;line1, city&quot;).fetch(&quot;billingAddress.country&quot;, &quot;*&quot;).fetch(&quot;contacts&quot;, &quot;firstName,email&quot;)
* .order().desc(&quot;id&quot;)
* .findList();
*
* JsonContext json = Ebean.createJsonContext();
*
* JsonWriteOptions writeOptions = new JsonWriteOptions();
* writeOptions.setRootPathVisitor(new JsonWriteBeanVisitor&lt;Customer&gt;() {
*
* public void visit(Customer bean, JsonWriter ctx) {
* System.out.println(&quot;write visit customer: &quot; + bean);
* ctx.appendKeyValue(&quot;dummyCust&quot;, &quot;34&quot;);
* ctx.appendKeyValue(&quot;smallCustObject&quot;, &quot;{\&quot;a\&quot;:34,\&quot;b\&quot;:\&quot;asdasdasd\&quot;}&quot;);
* }
* });
*
* writeOptions.setPathProperties(&quot;contacts&quot;, &quot;firstName,id&quot;);
* writeOptions.setPathVisitor(&quot;contacts&quot;, new JsonWriteBeanVisitor&lt;Contact&gt;() {
*
* public void visit(Contact bean, JsonWriter ctx) {
* System.out.println(&quot;write additional custom json on customer: &quot; + bean);
* ctx.appendKeyValue(&quot;dummy&quot;, &quot; 3400&quot; + bean.getId() + &quot;&quot;);
* ctx.appendKeyValue(&quot;smallObject&quot;, &quot;{\&quot;contactA\&quot;:34,\&quot;contactB\&quot;:\&quot;banana\&quot;}&quot;);
* }
*
* });
*
* // output as a JSON string with pretty formatting
* String s = json.toJsonString(list, true, writeOptions);
*
* </pre>
*
* @see JsonContext#toList(Class, String, JsonReadOptions)
*
* @author rbygrave
*
* @deprecated
*/
public class JsonWriteOptions {
protected String callback;
protected JsonValueAdapter valueAdapter;
protected Map<String, JsonWriteBeanVisitor<?>> visitorMap;
protected PathProperties pathProperties;
/**
* Parse and return a PathProperties from nested string format like
* (a,b,c(d,e),f(g)) where "c" is a path containing "d" and "e" and "f" is a
* path containing "g" and the root path contains "a","b","c" and "f".
*
* @see com.avaje.ebean.text.PathProperties#parse(String)
*/
public static JsonWriteOptions parsePath(String pathProperties) {
PathProperties p = PathProperties.parse(pathProperties);
return pathProperties(PathProperties.parse(pathProperties));
}
/**
* Construct JsonWriteOptions with the given pathProperties.
*/
public static JsonWriteOptions pathProperties(PathProperties pathProperties) {
JsonWriteOptions o = new JsonWriteOptions();
o.setPathProperties(p);
o.setPathProperties(pathProperties);
return o;
}
/**
* This creates and returns a copy of these options.
* <p>
* Note that it assumes that the JsonWriteBeanVisitor (if defined) are
* immutable and any JsonWriteBeanVisitor instances are shared between the
* original and the copy.
* </p>
*/
public JsonWriteOptions copy() {
JsonWriteOptions copy = new JsonWriteOptions();
copy.callback = callback;
copy.valueAdapter = valueAdapter;
copy.pathProperties = pathProperties;
if (visitorMap != null) {
copy.visitorMap = new HashMap<String, JsonWriteBeanVisitor<?>>(visitorMap);
}
return copy;
}
/**
* Return a JSONP callback function.
*/
public String getCallback() {
return callback;
}
/**
* Set a JSONP callback function.
*/
public JsonWriteOptions setCallback(String callback) {
this.callback = callback;
return this;
}
/**
* Return the JsonValueAdapter.
*/
public JsonValueAdapter getValueAdapter() {
return valueAdapter;
}
/**
* Set a JsonValueAdapter for custom DateTime and Date formatting.
*/
public JsonWriteOptions setValueAdapter(JsonValueAdapter valueAdapter) {
this.valueAdapter = valueAdapter;
return this;
}
/**
* Register a JsonWriteBeanVisitor for the root level.
*/
public JsonWriteOptions setRootPathVisitor(JsonWriteBeanVisitor<?> visitor) {
return setPathVisitor(null, visitor);
}
/**
* Register a JsonWriteBeanVisitor for the given path.
*/
public JsonWriteOptions setPathVisitor(String path, JsonWriteBeanVisitor<?> visitor) {
if (visitorMap == null) {
visitorMap = new HashMap<String, JsonWriteBeanVisitor<?>>();
}
visitorMap.put(path, visitor);
return this;
}
/**
* Set the properties to include in the JSON output for the given path.
*
* @param propertiesToInclude
* The set of properties to output
*/
public JsonWriteOptions setPathProperties(String path, Set<String> propertiesToInclude) {
if (pathProperties == null) {
pathProperties = new PathProperties();
}
pathProperties.put(path, propertiesToInclude);
return this;
}
/**
* Set the properties to include in the JSON output for the given path.
*
* @param propertiesToInclude
* Comma delimited list of properties to output
*/
public JsonWriteOptions setPathProperties(String path, String propertiesToInclude) {
return setPathProperties(path, parseProps(propertiesToInclude));
}
/**
* Set the properties to include in the JSON output for the root level.
*
* @param propertiesToInclude
* Comma delimited list of properties to output
*/
public JsonWriteOptions setRootPathProperties(String propertiesToInclude) {
return setPathProperties(null, parseProps(propertiesToInclude));
}
/**
* Set the properties to include in the JSON output for the root level.
*
* @param propertiesToInclude
* The set of properties to output
*/
public JsonWriteOptions setRootPathProperties(Set<String> propertiesToInclude) {
return setPathProperties(null, propertiesToInclude);
}
private Set<String> parseProps(String propertiesToInclude) {
LinkedHashSet<String> props = new LinkedHashSet<String>();
String[] split = propertiesToInclude.split(",");
for (int i = 0; i < split.length; i++) {
String s = split[i].trim();
if (s.length() > 0) {
props.add(s);
}
}
return props;
}
/**
* Return the Map of registered JsonWriteBeanVisitor's by path.
*/
public Map<String, JsonWriteBeanVisitor<?>> getVisitorMap() {
return visitorMap;
}
/**
* Set the Map of properties to include by path.
*/
@@ -1,28 +0,0 @@
package com.avaje.ebean.text.json;
/**
* The JSON Writer made available to JsonWriteBeanVisitor's so that you can
* append your own JSON content into the output.
*
* @see JsonWriteBeanVisitor
* @see JsonWriteOptions#setRootPathVisitor(JsonWriteBeanVisitor)
* @see JsonWriteOptions#setPathVisitor(String, JsonWriteBeanVisitor)
*
* @author rbygrave
*/
public interface JsonWriter {
/**
* Use this to append some custom content into the JSON output.
*
* @param key
* the json key
*
* @param rawJsonValue
* raw json value
*/
public void appendRawValue(String key, String rawJsonValue);
public void appendQuoteEscapeValue(String key, String rawJsonValue);
}
@@ -19,31 +19,10 @@
* .order().desc(&quot;id&quot;)
* .findList();
*
* JsonContext json = Ebean.createJsonContext();
*
* JsonWriteOptions writeOptions = new JsonWriteOptions();
* writeOptions.setRootPathVisitor(new JsonWriteBeanVisitor&lt;Customer&gt;() {
*
* public void visit(Customer bean, JsonWriter ctx) {
* System.out.println(&quot;write visit customer: &quot; + bean);
* ctx.appendKeyValue(&quot;dummyCust&quot;, &quot;34&quot;);
* ctx.appendKeyValue(&quot;smallCustObject&quot;, &quot;{\&quot;a\&quot;:34,\&quot;b\&quot;:\&quot;asdasdasd\&quot;}&quot;);
* }
* });
*
* writeOptions.setPathProperties(&quot;contacts&quot;, &quot;firstName,id&quot;);
* writeOptions.setPathVisitor(&quot;contacts&quot;, new JsonWriteBeanVisitor&lt;Contact&gt;() {
*
* public void visit(Contact bean, JsonWriter ctx) {
* System.out.println(&quot;write additional custom json on customer: &quot; + bean);
* ctx.appendKeyValue(&quot;dummy&quot;, &quot; 3400&quot; + bean.getId() + &quot;&quot;);
* ctx.appendKeyValue(&quot;smallObject&quot;, &quot;{\&quot;contactA\&quot;:34,\&quot;contactB\&quot;:\&quot;banana\&quot;}&quot;);
* }
*
* });
*
* // output as a JSON string with pretty formatting
* String s = json.toJsonString(list, true, writeOptions);
* JsonContext json = Ebean.json();
*
* // output as a JSON string
* String jsonOutput = json.toJson(list);
*
* </pre>
*/
@@ -31,6 +31,12 @@ public class BindParams implements Serializable {
*/
private String preparedSql;
/**
* Bind hash and count used to detect when the bind values have changed such
* that the generated SQL (with named parameters) needs to be recalculated.
*/
private int[] bindHash;
public BindParams() {
}
@@ -51,20 +57,33 @@ public class BindParams implements Serializable {
* </p>
*/
public void buildQueryPlanHash(HashQueryPlanBuilder builder) {
int[] vals = calcQueryPlanHash();
builder.add(vals[0]).bind(vals[1]);
}
/**
* Calculate and return a query plan bind hash with total bind count.
*/
public int[] calcQueryPlanHash() {
int tempBindCount;
int bc = 0;
int hc = 31;
for (Param param : positionedParameters) {
hc = hc * 31 + param.queryBindCount();
tempBindCount = param.queryBindCount();
bc += tempBindCount;
hc = hc * 31 + tempBindCount;
}
for (Map.Entry<String, Param> entry : namedParameters.entrySet()) {
tempBindCount = entry.getValue().queryBindCount();
bc += tempBindCount;
hc = hc * 31 + entry.getKey().hashCode();
hc = hc * 31 + entry.getValue().queryBindCount();
hc = hc * 31 + tempBindCount;
}
int bindCount = positionedParameters.size() + namedParameters.size();
builder.add(hc).bind(bindCount);
return new int[]{hc, bc};
}
/**
* Return a deep copy of the BindParams.
*/
@@ -110,7 +129,7 @@ public class BindParams implements Serializable {
* parameters ordered.
*/
public boolean requiresNamedParamsPrepare() {
return !namedParameters.isEmpty() && positionedParameters.isEmpty();
return !namedParameters.isEmpty();
}
/**
@@ -150,7 +169,7 @@ public class BindParams implements Serializable {
}
private Param getParam(String name) {
Param p = (Param) namedParameters.get(name);
Param p = namedParameters.get(name);
if (p == null) {
p = new Param();
namedParameters.put(name, p);
@@ -165,7 +184,7 @@ public class BindParams implements Serializable {
positionedParameters.add(new Param());
}
}
return (Param) positionedParameters.get(position - 1);
return positionedParameters.get(position - 1);
}
/**
@@ -253,7 +272,29 @@ public class BindParams implements Serializable {
return preparedSql;
}
/**
/**
* Return true if the bind hash and count has not changed.
*/
public boolean isSameBindHash() {
if (bindHash == null) {
bindHash = calcQueryPlanHash();
return false;
}
int[] oldPlan = bindHash;
bindHash = calcQueryPlanHash();
return bindHash[0] == oldPlan[0] && bindHash[1] == oldPlan[1];
}
/**
* Create a new positioned parameters orderedList.
*/
public OrderedList createOrderedList() {
positionedParameters.clear();
return new OrderedList(positionedParameters);
}
/**
* The bind parameters in the correct binding order.
* <p>
* This is the result of converting sql with named parameters
@@ -327,8 +368,6 @@ public class BindParams implements Serializable {
private Object outValue;
private int textLocation;
/**
* Construct a Parameter.
*/
@@ -368,17 +407,8 @@ public class BindParams implements Serializable {
}
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o == this) {
return true;
}
if (o instanceof Param) {
return hashCode() == o.hashCode();
}
return false;
}
return o != null && (o == this || (o instanceof Param) && hashCode() == o.hashCode());
}
/**
* Return true if this is an In parameter that needs to be bound before
@@ -463,21 +493,6 @@ public class BindParams implements Serializable {
this.outValue = out;
}
/**
* Return the location this parameter was found in the sql text.
*/
public int getTextLocation() {
return textLocation;
}
/**
* Set the location in the sql text this parameter was located. This is
* used to control order for named parameters.
*/
public void setTextLocation(int textLocation) {
this.textLocation = textLocation;
}
/**
* If true do not include this value in a transaction log.
*/
@@ -11,6 +11,8 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
*/
public interface LoadBeanBuffer {
public int getBatchSize();
public List<EntityBeanIntercept> getBatch();
public BeanDescriptor<?> getBeanDescriptor();
@@ -59,4 +59,7 @@ public class LoadBeanRequest extends LoadRequest {
return lazyLoadProperty;
}
public int getBatchSize() {
return getLoadContext().getBatchSize();
}
}
@@ -11,16 +11,16 @@ import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
*/
public interface LoadContext {
/**
* Return the minimum batch size when using QueryIterator with query joins.
*/
public int getSecondaryQueriesMinBatchSize(OrmQueryRequest<?> parentRequest, int defaultQueryBatch);
/**
* Return the minimum batch size when using QueryIterator with query joins.
*/
public int getSecondaryQueriesMinBatchSize(OrmQueryRequest<?> parentRequest, int defaultQueryBatch);
/**
* Execute any secondary (+query) queries if there are any defined.
* @param parentRequest the originating query request
*/
public void executeSecondaryQueries(OrmQueryRequest<?> parentRequest, int defaultQueryBatch);
public void executeSecondaryQueries(OrmQueryRequest<?> parentRequest);
/**
* Register any secondary queries (+query or +lazy) with their
@@ -44,8 +44,11 @@ public interface LoadContext {
/**
* Set the persistence context used by this query and future lazy loading.
* <p>
* Used by query iterator when processing large result sets.
* </p>
*/
public void setPersistenceContext(PersistenceContext persistenceContext);
public void resetPersistenceContext(PersistenceContext persistenceContext);
/**
* Register a Bean for lazy loading.
@@ -13,6 +13,8 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
*/
public interface LoadManyBuffer {
public int getBatchSize();
public List<BeanCollection<?>> getBatch();
public BeanPropertyAssocMany<?> getBeanProperty();
@@ -18,12 +18,11 @@ public class LoadManyRequest extends LoadRequest {
private final boolean loadCache;
public LoadManyRequest(LoadManyBuffer loadContext, int batchSize, boolean lazy,boolean onlyIds, boolean loadCache) {
this(loadContext, null, batchSize, lazy, onlyIds, loadCache);
public LoadManyRequest(LoadManyBuffer loadContext, boolean lazy, boolean onlyIds, boolean loadCache) {
this(loadContext, null, lazy, onlyIds, loadCache);
}
public LoadManyRequest(LoadManyBuffer loadContext, OrmQueryRequest<?> parentRequest, int batchSize, boolean lazy,
boolean onlyIds, boolean loadCache) {
public LoadManyRequest(LoadManyBuffer loadContext, OrmQueryRequest<?> parentRequest, boolean lazy, boolean onlyIds, boolean loadCache) {
super(parentRequest, lazy);
this.loadContext = loadContext;
@@ -69,4 +68,10 @@ public class LoadManyRequest extends LoadRequest {
return loadCache;
}
/**
* Return the batch size used for this load context.
*/
public int getBatchSize() {
return loadContext.getBatchSize();
}
}
@@ -13,9 +13,6 @@ public interface LoadSecondaryQuery {
/**
* Execute the secondary query with a given batch size.
*
* @param parentRequest
* the originating query request
*/
public void loadSecondaryQuery(OrmQueryRequest<?> parentRequest, int requestedBatchSize, boolean all);
public void loadSecondaryQuery(OrmQueryRequest<?> parentRequest);
}
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.api;
import java.util.ArrayList;
import com.avaje.ebean.TxScope;
import com.avaje.ebean.config.PersistBatch;
/**
* Used internally to handle the scoping of transactions for methods.
@@ -10,14 +11,14 @@ import com.avaje.ebean.TxScope;
public class ScopeTrans implements Thread.UncaughtExceptionHandler {
private static final int OPCODE_ATHROW = 191;
//private static final int OPCODE_ATHROW = com.avaje.ebean.enhance.asm.Opcodes.ATHROW;
private final SpiTransactionScopeManager scopeMgr;
/**
* The suspended transaction (can be null).
*/
private final SpiTransaction suspendedTransaction;
/**
* The transaction in scope (can be null).
*/
@@ -44,7 +45,13 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
*/
private final ArrayList<Class<? extends Throwable>> rollbackFor;
/**
private PersistBatch restoreBatch;
private PersistBatch restoreBatchOnCascade;
private int restoreBatchSize;
/**
* Flag set when a rollback has occurred.
*/
private boolean rolledBack;
@@ -61,6 +68,24 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
this.noRollbackFor = txScope.getNoRollbackFor();
this.rollbackFor = txScope.getRollbackFor();
if (transaction != null) {
if (!created && txScope.isBatchSet() || txScope.isBatchOnCascadeSet() || txScope.isBatchSizeSet()) {
restoreBatch = transaction.getBatch();
restoreBatchOnCascade = transaction.getBatchOnCascade();
restoreBatchSize = transaction.getBatchSize();
}
if (txScope.isBatchSet()) {
transaction.setBatch(txScope.getBatch());
}
if (txScope.isBatchOnCascadeSet()) {
transaction.setBatchOnCascade(txScope.getBatchOnCascade());
}
if (txScope.isBatchSizeSet()) {
transaction.setBatchSize(txScope.getBatchSize());
}
}
}
/**
@@ -97,10 +122,22 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
*/
public void onFinally() {
try {
if (!rolledBack && created) {
transaction.commit();
if (!rolledBack) {
if (created) {
transaction.commit();
} else {
if (restoreBatch != null) {
transaction.setBatch(restoreBatch);
}
if (restoreBatchOnCascade != null) {
transaction.setBatchOnCascade(restoreBatchOnCascade);
}
if (restoreBatchSize > 0) {
transaction.setBatchSize(restoreBatchSize);
}
}
}
} finally {
if (suspendedTransaction != null){
// put the previously suspended transaction
@@ -2,10 +2,7 @@ package com.avaje.ebeaninternal.api;
import java.util.List;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Query;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.TxScope;
import com.avaje.ebean.*;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.BeanLoader;
import com.avaje.ebean.bean.CallStack;
@@ -63,6 +60,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
public CallStack createCallStack();
/**
* Return the PersistenceContextScope to use defined at query or server level.
*/
public PersistenceContextScope getPersistenceContextScope(SpiQuery<?> query);
/**
* Return the DDL generator.
*/
@@ -44,9 +44,14 @@ public interface SpiExpressionRequest {
* Return the ordered list of bind values for all expressions in this request.
*/
public ArrayList<Object> getBindValues();
/**
* Increments the parameter index and returns that value.
*/
public int nextParameter();
/**
* Increments the parameter index and returns that value.
*/
public int nextParameter();
/**
* Append a DB Like clause.
*/
public void appendLike();
}
@@ -5,6 +5,7 @@ import java.util.List;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.Query;
import com.avaje.ebean.bean.BeanCollectionTouched;
import com.avaje.ebean.bean.CallStack;
@@ -26,7 +27,7 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
*/
public interface SpiQuery<T> extends Query<T> {
public enum Mode {
public enum Mode {
NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true);
Mode(boolean loadContextBean) {
this.loadContextBean = loadContextBean;
@@ -79,7 +80,20 @@ public interface SpiQuery<T> extends Query<T> {
*/
SUBQUERY
}
/**
* Return the PersistenceContextScope that this query should use.
* <p>
* This can be null and in that case use the default scope.
* </p>
*/
public PersistenceContextScope getPersistenceContextScope();
/**
* Return the default lazy load batch size.
*/
public int getLazyLoadBatchSize();
/**
* Return true if select all properties was used to ensure the property
* invoking a lazy load was included in the query.
@@ -597,4 +611,9 @@ public interface SpiQuery<T> extends Query<T> {
* Return true if this query has been cancelled.
*/
public boolean isCancelled();
/**
* Return root table alias set by {@link #alias(String)} command.
*/
public String getAlias();
}
@@ -5,4 +5,6 @@ import com.avaje.ebean.SqlUpdate;
public interface SpiSqlUpdate extends SqlUpdate {
public BindParams getBindParams();
public void setGeneratedSql(String sql);
}
@@ -5,6 +5,8 @@ import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.persist.BatchControl;
/**
@@ -19,179 +21,211 @@ public interface SpiTransaction extends Transaction {
* End the transaction when had query only use.
*/
public void endQueryOnly();
/**
* Return the string prefix with the transactin id and label used in logging.
*/
public String getLogPrefix();
/**
* Return true if generated SQL and Bind values should be logged to the
* transaction log.
*/
public boolean isLogSql();
/**
* Return true if summary level events should be logged to the transaction
* log.
*/
public boolean isLogSummary();
/**
* Return true if generated SQL and Bind values should be logged to the
* transaction log.
*/
public boolean isLogSql();
/**
* Log a message to the SQL logger.
*/
public void logSql(String msg);
/**
* Return true if summary level events should be logged to the transaction
* log.
*/
public boolean isLogSummary();
/**
* Log a message to the SUMMARY logger.
*/
public void logSummary(String msg);
/**
* Log a message to the SQL logger.
*/
public void logSql(String msg);
/**
* Register a "Derived Relationship" (that requires an additional update).
*/
public void registerDerivedRelationship(DerivedRelationshipData assocBean);
/**
* Log a message to the SUMMARY logger.
*/
public void logSummary(String msg);
/**
* Return the list of "Derived Relationships" that must be maintained after
* insert.
*/
public List<DerivedRelationshipData> getDerivedRelationship(Object bean);
/**
* Register a "Derived Relationship" (that requires an additional update).
*/
public void registerDerivedRelationship(DerivedRelationshipData assocBean);
/**
* Add a deleting bean to the registered list.
* <p>
* This is to handle bi-directional relationships where both sides Cascade.
* </p>
*/
public void registerDeleteBean(Integer hash);
/**
* Return the list of "Derived Relationships" that must be maintained after
* insert.
*/
public List<DerivedRelationshipData> getDerivedRelationship(Object bean);
/**
* Unregister the hash of the bean.
*/
public void unregisterDeleteBean(Integer hash);
/**
* Add a deleting bean to the registered list.
* <p>
* This is to handle bi-directional relationships where both sides Cascade.
* </p>
*/
public void registerDeleteBean(Integer hash);
/**
* Return true if this is a bean that has already been saved/deleted.
*/
public boolean isRegisteredDeleteBean(Integer hash);
/**
* Unregister the hash of the bean.
*/
public void unregisterDeleteBean(Integer hash);
/**
* Unregister the persisted bean.
*/
public void unregisterBean(Object bean);
/**
* Return true if this is a bean that has already been saved/deleted.
*/
public boolean isRegisteredDeleteBean(Integer hash);
/**
* Return true if this is a bean that has already been persisted in the
* current recursive save request. The goal is to stop recursively saving
* the bean when cascade persist is on both sides of a relationship).
* <p>
* This will register the bean if it is not already.
* </p>
*/
public boolean isRegisteredBean(Object bean);
/**
* Unregister the persisted bean.
*/
public void unregisterBean(Object bean);
/**
* Returns a String used to identify the transaction. This id is used for
* Transaction logging.
*/
public String getId();
/**
* Return true if this is a bean that has already been persisted in the
* current recursive save request. The goal is to stop recursively saving
* the bean when cascade persist is on both sides of a relationship).
* <p>
* This will register the bean if it is not already.
* </p>
*/
public boolean isRegisteredBean(Object bean);
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
* Returning 0 implies to use the system wide default batch size.
* </p>
*/
public int getBatchSize();
/**
* Returns a String used to identify the transaction. This id is used for
* Transaction logging.
*/
public String getId();
/**
* Modify and return the current 'depth' of the transaction.
* <p>
* As we cascade save or delete we traverse the object graph tree. Going up
* to Assoc Ones the depth decreases and going down to Assoc Manys the depth
* increases.
* </p>
* <p>
* The depth is used for ordering batching statements. The lowest depth get
* executed first during save.
* </p>
*/
public int depth(int diff);
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
* Returning 0 implies to use the system wide default batch size.
* </p>
*/
public int getBatchSize();
/**
* Return true if this transaction was created explicitly via
* <code>Ebean.beginTransaction()</code>.
*/
public boolean isExplicit();
/**
* Modify and return the current 'depth' of the transaction.
* <p>
* As we cascade save or delete we traverse the object graph tree. Going up
* to Assoc Ones the depth decreases and going down to Assoc Manys the depth
* increases.
* </p>
* <p>
* The depth is used for ordering batching statements. The lowest depth get
* executed first during save.
* </p>
*/
public int depth(int diff);
/**
* Get the object that holds the event details.
* <p>
* This information is used maintain the table state, cache and text
* indexes. On commit the Table modifications this generates is broadcast
* around the cluster (if you have a cluster).
* </p>
*/
public TransactionEvent getEvent();
/**
* Return the current depth.
*/
public int depth();
/**
* Whether persistCascade is on for save and delete.
*/
public boolean isPersistCascade();
/**
* Return true if this transaction was created explicitly via
* <code>Ebean.beginTransaction()</code>.
*/
public boolean isExplicit();
/**
* Return true if this request should be batched. Conversely returns false
* if this request should be executed immediately.
*/
public boolean isBatchThisRequest();
/**
* Get the object that holds the event details.
* <p>
* This information is used maintain the table state, cache and text
* indexes. On commit the Table modifications this generates is broadcast
* around the cluster (if you have a cluster).
* </p>
*/
public TransactionEvent getEvent();
/**
* Return the queue used to batch up persist requests.
*/
public BatchControl getBatchControl();
/**
* Whether persistCascade is on for save and delete.
*/
public boolean isPersistCascade();
/**
* Set the queue used to batch up persist requests. There should only be one
* PersistQueue set per transaction.
*/
public void setBatchControl(BatchControl control);
/**
* Return true if this request should be batched. Conversely returns false
* if this request should be executed immediately.
*/
public boolean isBatchThisRequest(PersistRequest.Type type);
/**
* Return the persistence context associated with this transaction.
* <p>
* You may wish to hold onto this and set it against another transaction
* later. This is along the lines of 'extended persistence context'
* behaviour.
* </p>
*/
public PersistenceContext getPersistenceContext();
/**
* Return the queue used to batch up persist requests.
*/
public BatchControl getBatchControl();
/**
* Set the persistence context to this transaction.
* <p>
* This could be considered similar to 'EJB3 Extended Persistence Context'.
* In that you can get the PersistenceContext from a transaction, hold onto
* it, and then set it back later to a second transaction. In general there
* is one PersistenceContext per Transaction. The getPersistenceContext()
* and setPersistenceContext() enable a developer to reuse a single
* PersistenceContext with multiple transactions.
* </p>
*/
public void setPersistenceContext(PersistenceContext context);
/**
* Set the queue used to batch up persist requests. There should only be one
* PersistQueue set per transaction.
*/
public void setBatchControl(BatchControl control);
/**
* Return the underlying Connection for internal use.
* <p>
* If the connection is made public from Transaction and the user code calls
* that method we can no longer trust the query only status of a
* Transaction.
* </p>
*/
public Connection getInternalConnection();
/**
* Return the persistence context associated with this transaction.
* <p>
* You may wish to hold onto this and set it against another transaction
* later. This is along the lines of 'extended persistence context'
* behaviour.
* </p>
*/
public PersistenceContext getPersistenceContext();
/**
* Return true if the manyToMany intersection should be persisted for this particular relationship direction.
*/
/**
* Set the persistence context to this transaction.
* <p>
* This could be considered similar to 'EJB3 Extended Persistence Context'.
* In that you can get the PersistenceContext from a transaction, hold onto
* it, and then set it back later to a second transaction. In general there
* is one PersistenceContext per Transaction. The getPersistenceContext()
* and setPersistenceContext() enable a developer to reuse a single
* PersistenceContext with multiple transactions.
* </p>
*/
public void setPersistenceContext(PersistenceContext context);
/**
* Return the underlying Connection for internal use.
* <p>
* If the connection is made public from Transaction and the user code calls
* that method we can no longer trust the query only status of a
* Transaction.
* </p>
*/
public Connection getInternalConnection();
/**
* Return true if the manyToMany intersection should be persisted for this particular relationship direction.
*/
public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName);
/**
* Return true if batch mode got escalated for this request (and associated cascades).
*/
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request);
/**
* If batch mode was turned on for the request then flush the batch.
*/
public void flushBatchOnCascade();
/**
* Mark the transaction explicitly as not being query only.
*/
public void markNotQueryOnly();
/**
* Potentially escalate batch mode on saving or deleting a collection.
*/
public void checkBatchEscalationOnCollection();
/**
* Flush batch if we escalated batch mode on saving or deleting a collection.
*/
public void flushBatchOnCollection();
}
@@ -1,19 +1,11 @@
package com.avaje.ebeaninternal.api;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.transaction.BeanDelta;
import com.avaje.ebeaninternal.server.transaction.DeleteByIdMap;
import com.avaje.ebeaninternal.server.transaction.IndexInvalidate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.List;
/**
* Holds information for a transaction. There is one TransactionEvent instance
@@ -25,180 +17,107 @@ import org.slf4j.LoggerFactory;
*/
public class TransactionEvent implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(TransactionEvent.class);
private static final long serialVersionUID = 7230903304106097120L;
private static final long serialVersionUID = 7230903304106097120L;
/**
* Flag indicating this is a local transaction (not from another server in
* the cluster).
*/
private transient boolean local;
/**
* Flag indicating this is a local transaction (not from another server in
* the cluster).
*/
private transient boolean local;
private boolean invalidateAll;
private TransactionEventTable eventTables;
private TransactionEventTable eventTables;
private transient TransactionEventBeans eventBeans;
private transient TransactionEventBeans eventBeans;
private transient List<BeanDelta> beanDeltas;
private transient DeleteByIdMap deleteByIdMap;
private transient DeleteByIdMap deleteByIdMap;
private transient Set<IndexInvalidate> indexInvalidations;
private transient Set<String> pauseIndexInvalidate;
/**
* Create the TransactionEvent, one per Transaction.
*/
public TransactionEvent() {
this.local = true;
}
/**
* Set this to true to invalidate all table dependent cached objects.
*/
public void setInvalidateAll(boolean isInvalidateAll) {
this.invalidateAll = isInvalidateAll;
}
/**
* Create the TransactionEvent, one per Transaction.
*/
public TransactionEvent() {
this.local = true;
}
/**
* Return true if all table states should be invalidated. This will cause
* all cached objects to be invalidated.
*/
public boolean isInvalidateAll() {
return invalidateAll;
}
/**
* Temporarily pause/ignore any index invalidation for this bean type.
*/
public void pauseIndexInvalidate(Class<?> beanType) {
if (pauseIndexInvalidate == null){
pauseIndexInvalidate = new HashSet<String>();
}
pauseIndexInvalidate.add(beanType.getName());
}
/**
* Resume listening for index invalidation for this bean type.
*/
public void resumeIndexInvalidate(Class<?> beanType) {
if (pauseIndexInvalidate != null){
pauseIndexInvalidate.remove(beanType.getName());
}
public void addDeleteById(BeanDescriptor<?> desc, Object id) {
if (deleteByIdMap == null) {
deleteByIdMap = new DeleteByIdMap();
}
/**
* Add an IndexInvalidation notices to the transaction.
*/
public void addIndexInvalidate(IndexInvalidate indexEvent){
if (pauseIndexInvalidate != null && pauseIndexInvalidate.contains(indexEvent.getIndexName())){
logger.debug("--- IGNORE Invalidate on "+indexEvent.getIndexName());
return;
}
if (indexInvalidations == null){
indexInvalidations = new HashSet<IndexInvalidate>();
}
indexInvalidations.add(indexEvent);
}
public void addDeleteById(BeanDescriptor<?> desc, Object id){
if (deleteByIdMap == null){
deleteByIdMap = new DeleteByIdMap();
}
deleteByIdMap.add(desc, id);
}
public void addDeleteByIdList(BeanDescriptor<?> desc, List<Object> idList) {
if (deleteByIdMap == null) {
deleteByIdMap = new DeleteByIdMap();
}
deleteByIdMap.addList(desc, idList);
deleteByIdMap.add(desc, id);
}
public void addDeleteByIdList(BeanDescriptor<?> desc, List<Object> idList) {
if (deleteByIdMap == null) {
deleteByIdMap = new DeleteByIdMap();
}
public DeleteByIdMap getDeleteByIdMap() {
return deleteByIdMap;
deleteByIdMap.addList(desc, idList);
}
public DeleteByIdMap getDeleteByIdMap() {
return deleteByIdMap;
}
/**
* Return true if this was a local transaction. Returns false if this
* transaction originated on another server in the cluster.
*/
public boolean isLocal() {
return local;
}
/**
* For BeanListeners the requests they are interested in.
*/
public TransactionEventBeans getEventBeans() {
return eventBeans;
}
public TransactionEventTable getEventTables() {
return eventTables;
}
public void add(String tableName, boolean inserts, boolean updates, boolean deletes) {
if (eventTables == null) {
eventTables = new TransactionEventTable();
}
eventTables.add(tableName, inserts, updates, deletes);
}
public void addBeanDelta(BeanDelta delta) {
if (beanDeltas == null) {
beanDeltas = new ArrayList<BeanDelta>();
}
beanDeltas.add(delta);
public void add(TransactionEventTable table) {
if (eventTables == null) {
eventTables = new TransactionEventTable();
}
eventTables.add(table);
}
public List<BeanDelta> getBeanDeltas() {
return beanDeltas;
/**
* Add a inserted updated or deleted bean to the event.
*/
public void add(PersistRequestBean<?> request) {
if (request.isNotify()) {
// either a BeanListener or Cache is interested
if (eventBeans == null) {
eventBeans = new TransactionEventBeans();
}
eventBeans.add(request);
}
/**
* Return true if this was a local transaction. Returns false if this
* transaction originated on another server in the cluster.
*/
public boolean isLocal() {
return local;
}
}
/**
* For BeanListeners the requests they are interested in.
*/
public TransactionEventBeans getEventBeans() {
return eventBeans;
}
public TransactionEventTable getEventTables() {
return eventTables;
}
public Set<IndexInvalidate> getIndexInvalidations() {
return indexInvalidations;
/**
* Notify the cache of bean changes.
* <p>
* This returns the TransactionEventTable so that if any
* general table changes can also be used to invalidate
* parts of the cache.
* </p>
*/
public void notifyCache() {
if (eventBeans != null) {
eventBeans.notifyCache();
}
public void add(String tableName, boolean inserts, boolean updates, boolean deletes){
if (eventTables == null){
eventTables = new TransactionEventTable();
}
eventTables.add(tableName, inserts, updates, deletes);
}
public void add(TransactionEventTable table){
if (eventTables == null){
eventTables = new TransactionEventTable();
}
eventTables.add(table);
}
/**
* Add a inserted updated or deleted bean to the event.
*/
public void add(PersistRequestBean<?> request) {
if (request.isNotify(this)){
// either a BeanListener or Cache is interested
if (eventBeans == null) {
eventBeans = new TransactionEventBeans();
}
eventBeans.add(request);
}
}
/**
* Notify the cache of bean changes.
* <p>
* This returns the TransactionEventTable so that if any
* general table changes can also be used to invalidate
* parts of the cache.
* </p>
*/
public void notifyCache(){
if (eventBeans != null){
eventBeans.notifyCache();
}
if (deleteByIdMap != null) {
deleteByIdMap.notifyCache();
}
}
if (deleteByIdMap != null) {
deleteByIdMap.notifyCache();
}
}
}
@@ -75,7 +75,7 @@ public final class TransactionEventTable implements Serializable {
private boolean update;
private boolean delete;
private TableIUD(String table, boolean insert, boolean update, boolean delete){
public TableIUD(String table, boolean insert, boolean update, boolean delete){
this.table = table;
this.insert = insert;
this.update = update;
@@ -1,18 +1,16 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import javax.persistence.PersistenceException;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.resource.ResourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class AutoFetchManagerFactory {
private static final Logger logger = LoggerFactory.getLogger(AutoFetchManagerFactory.class);
@@ -37,7 +35,7 @@ public class AutoFetchManagerFactory {
AutoFetchManager autoFetchManager = null;
boolean readFile = GlobalProperties.getBoolean("autofetch.readfromfile", true);
boolean readFile = !"false".equalsIgnoreCase(System.getProperty("autofetch.readfromfile"));
if (readFile) {
autoFetchManager = deserializeAutoFetch(autoFetchFile);
}

Some files were not shown because too many files have changed in this diff Show More