BoxLang 🚀 A New JVM Dynamic Language Learn More...
This module will enhance your experience when working with the ColdFusion (CFML) ORM powered by Hibernate. It will not only enhance it with dynamic goodness but give you a fluent and human approach to working with Hibernate. It will finally make working with ORM NOT SUCK!
# A quick preview of some functionality
var book = new Book().findByTitle( "My Awesome Book" );
var book = new Book().getOrFail( 2 );
property name="userService" inject="entityService:User";
return userService.list();
return userService.list( asStream=true );
userService
.newCriteria()
.eq( "name", "luis" )
.isTrue( "isActive" )
.getOrFail();
userService
.newCriteria()
.isTrue( "isActive" )
.joinTo( "role" )
.eq( "name", "admin" )
.asStream()
.list();
userService
.newCriteria()
.withProjections( property="id,fname:firstName,lname:lastName,age" )
.isTrue( "isActive" )
.joinTo( "role" )
.eq( "name", "admin" )
.asStruct()
.list();
Apache License, Version 2.0.
Source & Changelog
Documentation
Issues
Support
Use CommandBox cli to install:
box install cborm
Unfortunately, due to the way that ORM is loaded by ColdFusion, if you are using the ORM EventHandler or ActiveEntity
or any ColdBox Proxies that require ORM, you must create an Application Mapping in the Application.cfc
like this:
this.mappings[ "/cborm" ] = COLDBOX_APP_ROOT_PATH & "modules/cborm";
This is due to the fact that the ORM event listener starts before ColdBox, so no dynamic mappings exist yet. Important: Make sure you ALWAYS lazy load dependencies in your event handlers to avoid chicken and the egg issues.
The module also registers a new WireBox DSL called entityservice
which can produce virtual or base ORM entity services:
entityservice
- Inject a global ORM service so you can work with ANY entityentityservice:{entityName}
- Inject a Virtual entity service according to entityName
Here are the module settings you can place in your config/Coldbox.cfc
under the moduleSettings.cborm
structure:
moduleSettings = {
cborm = {
// Resource Settings
resources : {
// Enable the ORM Resource Event Loader
eventLoader : false,
// Prefix to use on all the registered pre/post{Entity}{Action} events
eventPrefix : "",
// Pagination max rows
maxRows : 25,
// Pagination max row limit: 0 = no limit
maxRowsLimit : 500
},
// WireBox Injection Bridge
injection : {
enabled : true,
include : "",
exclude : ""
}
}
};
We have also migrated the UniqueValidator
from the validation module into our
ORM module. It is mapped into wirebox as UniqueValidator@cborm
so you can use in your constraints like so:
this.constraints = {
"name" : {
"required":true,
"validator":"UniqueValidator@cborm"
}
};
All contributions welcome! Feel free to fix a typo, add a feature 🚀 , or add a testbox spec for a newly discovered issue 🐛
If you want to get hacking on CBORM, here's how to start:
docker-compose.yml
file. Just make sure you have Docker installed. run-script startdbs
or run it manually below.docker run \
-e MYSQL_RANDOM_ROOT_PASSWORD=yes \
-e MYSQL_USER=other \
-e MYSQL_PASSWORD=ortussolutions \
-e MYSQL_DATABASE=coolblog \
-v "$PWD/test-harness/tests/resources":/docker-entrypoint-initdb.d \
-p 3306:3306 \
--detach \
--name cborm_mysql \
mysql
.env.template
to .env
and enter the database credentials used in step 2 above ☝box run-script install:dependencies
box start [email protected]
(You can use adobe or other engines)Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp www.ortussolutions.com
Because of His grace, this project exists. If you don't like this, then don't read it, its not for you.
"Therefore being justified by faith, we have peace with God through our Lord Jesus Christ: By whom also we have access by faith into this grace wherein we stand, and rejoice in hope of the glory of God. And not only so, but we glory in tribulations also: knowing that tribulation worketh patience; And patience, experience; and experience, hope: And hope maketh not ashamed; because the love of God is shed abroad in our hearts by the Holy Ghost which is given unto us. ." Romans 5:5
"I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" Jn 14:1-12
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
lazy
annotations that conflict with cb7 lazy propertiesprocessState()
to announce()
on all testsprocessState()
to announce()
to stay compliantannounceInterception()
to announce()
to stay compliantwhen( boolean, success, fail )
fluent construct for ActiveEntity
, VirtualEntityService
and the BaseORMService
to allow for fluent chaining of operations on an entity or it's service.countWhere()
invalid SQL exception if no arguments are provided: https://github.com/coldbox-modules/cborm/pull/54Subqueries
was marked as a singleton when indeed it was indeed a transient. This could have created scoping issues on subquery based detached criteria building.BaseBuilder
detached projectionsDetachedCriteriaBuilder
was not passing the datasource
to native criteria objectsdocker-compose.yml
to startup MySQL, or PostgreSQL in docker, for further hacking and testing.buildJavaProxy()
which leverages our JavaProxyBuilder
evict()
had the wrong method and arguments delegated to the parent class.isInTransaction()
util helper method to all the orm services.ORMFlush, ORMAutoFlush, ORMPreFlush, ORMDirtyCheck, ORMEvict, and ORMClear
?
has been deprecated. You will have to use the ?x
approach where x
is a number according to the position in the sql:// Old Syntax
select p
from Person p
where p.name like ? and p.isStatus = ?
// New Syntax
select p
from Person p
where p.name like ?1 and p.isStatus = ?2
eventPrefix
setting so you can prefix the resource REST CRUD events with whatever you like.results
struct does not have the required keysvariables.saveMethod
property or the savemethod
argument.variables.deleteMethod
property or the deleteMethod
argument.getSQLHelper()
from criterias to allow for usage of formmatting of sqlbeforeOrmExecuteQuery, afterOrmExecuteQuery
from the base orm service: executeQuery()
methodafterCriteriaBuilderList
event before results conversionsafterCriteriaBuilderGet
, beforeCriteriaBuilderGet
called after/before criteria get()
callsnew()
method instead of being auto-wired after population.processEntityInjection()
public on the ORM Event Handler so it can be reused in other locationsprocessEntityInjection()
returns the passed entity so you can do chaininggetOrFail()
now includes in the extendedInfo
the actual entity that caused the exceptionORMUtilSupport
when detecting datasources, if you passed a default it would never be usedpostNew
was not using the actual entity name so we where hitting performance on lookups for namefalse
for resource handler and it needed to be true
Features
: Introduction of the automatic resource handler for ORM Entities based on ColdBox's 6 resources and RestHandlerImprovement
: Natively allow for nested transactions and savepoints by not doing preemptive transaction commits when using transactions.Bug
: Fix on getOrFail()
where if the id was 0, it would still return an empty object.Task
: Added formatting via cfformatFeature
: Upgraded to cbValidation
2.0.0Feature
: Updated the unique validator to match 2.0.0 standardsFeature
: Upgraded to mementifier
2.0.0improvement
: In executeQuery()
Determine if we are in a UPDATE, INSERT or DELETE, if we do, just return the results instead of a stream or query as the result is always numeric, the rows that were altered.bug
: Fixed asStream
typo on executeQuery()
bug
: Missing ACF2016 compat on testsbug
: virtual entity service still had entity
required for casting methodsFeature
: New function for criteria query when( boolean, target )
that you can use to build functional criterias without the use of if statements.
newCriteria() .when( isBoolean( arguments.isPublished ), function( c ){ // Published bit c.isEq( "isPublished", isPublished ); // Published eq true evaluate other params if( isPublished ){ c.isLt( "publishedDate", now() ) .$or( c.restrictions.isNull( "expireDate" ), c.restrictions.isGT( "expireDate", now() ) ) .isEq( "passwordProtection","" ); } } ) .when( !isNull( arguments.showInSearch ), function( criteria ){ c.isEq( "showInSearch", showInSearch ); } ) .list()
Feature
: Missing nullValue()
is BaseBuilder class
Feature
: Added new criteria query peek( closure )
function to allow for peeking into the building process. Pass in your closure that receives the criteria and interact with it.
Feature
: Added a validateOrFail()
to the active entity, which if the validation fails it will throw an exception or return back to you the same entity validated now.
Improvement
: Better documentation for deleteById()
since it does bulk deletion, which does not do any type of cascading.
Improvement
: isValid()
in active entity missing includeFields
argument
Improvement
: Timeout hints for criteria builder
Improvement
: Updated exception type for criteria builder get()
Bug
: ACF2016 issues with elvis operator.
Bug
: getOrFail()
had an invalid throw statement
populate()
in ActiveEntity so the target is the last argument so you can just pass a struct as the first argument #29save()
operation return the saved entity or array of entities instead of the BaseORM service #28orm
configuration structure in your config/ColdBox.cfc
to the moduleSettings
struct and rename it to cborm
to standardize it to module settings.moduleSettings = {
cborm = {
inject = {
enabled = true,
includes = "",
excludes = ""
}
}
};
deleteByQuery()
reworked entirely to do native bulk delete queries. It now also returns the number of records removedevict()
method was renamed to evictCollection()
to better satisfy the same contract in hibernateevictEntity()
method was renamed to evict()
to better satisfay the same contract in hibernatebyExample
on many listing methodsget()
-> getOrFail()
to throw an entity not found exceptionasStruct(), asStream(), asDistinct()
that will apply result transformers for you instead of doing .resultTransformer( c.ALIAS_TO_ENTITY_MAP )
, whish is long and boring, or return to you a java stream via cbStreams.not
. So you can do: .notEq(), notBetween(), notIsNull(), notIsIn()
and much more.list()
method has a new asStream
boolean argument that if true, will return the results as a cbStream. ((www.forgebox.io/view/cbStreams))idCast()
and autoCast()
added for quick casting of valuesqueryHint()
so you can add your own vendor specific query hints for optimizers.comment( string )
so you can add arbitrary comments to the generated SQL, great for debuggingsqlRestriction()
deprecated in favor of the shorthand notation: sql()
sql()
restriction now supports binding positional parameters. You can pass them in an array and we will infer the types: sql( "id = ? and isActive = ?", [ "123", true ] )
. Or you can pass in a struct of {value:"", type:""}
instead:restrictions.sql( "userName = ? and firstName like ?", [
{ value : "joe", type : "string" },
{ value : "%joe%", type : "string" }
] );
The available types are the following which match the Hibernate Types
this.TYPES = {
"string" : "StringType",
"clob" : "ClobType",
"text" : "TextType",
"char" : "ChareacterType",
"boolean" : "BooleanType",
"yesno" : "YesNoType",
"truefalse" : "TrueFalseType",
"byte" : "ByteType",
"short" : "ShortType",
"integer" : "IntegerType",
"long" : "LongType",
"float" : "FloatType",
"double" : "DoubleType",
"bigInteger" : "BigIntegerType",
"bigDecimal" : "BigDecimalType",
"timestamp" : "TimestampType",
"time" : "TimeType",
"date" : "DateType",
"calendar" : "CalendarType",
"currency" : "CurrencyType",
"locale" : "LocaleType",
"timezone" : "TimeZoneType",
"url" : "UrlType",
"class" : "ClassType",
"blob" : "BlobType",
"binary" : "BinaryType",
"uuid" : "UUIDCharType",
"serializable" : "SerializableType"
};
maxResults( maxResults )
method to limit the results bygetOrFail() proxies to get(), findOrFail() proxies to findIt()
that if not entity is produced will throw a EntityNotFound
exceptionasStream
boolean argument.criteriaCount(), criteriaQuery()
from BaseService, this was the legacy criteria builder approach, please use newCriteria()
instead.getEntityGivenName
to support ACF2018BeanPopulator
for performance on creationsORMEventHandler
for performance on creationsrestrictions
for performance on creationsdatasource
, or uses the default one declareddatasource
to many listing methodsignoreCase, sorting and timeouts
.getAll()
to retrieve read only entities using the readOnly
argument.getAll()
method has a new properties
argument that if passed will allow you to retrieve an array of structs according to the passed in properties.idCast( entity, id )
to auto cast your entity id
value to java type automatically for you, no more javacastingautoCast( entity, propertyName, value )
to auto cast any value for any entity property automatically, no more javacasting.getKeyValue( entity )
which will give you the value of the entity's unique identifierisDirty( entity )
which will let you know if the entity has dirty values or has its values changed since loaded from the dbgetEntityMetadata( entity )
which will return to you the hibernate's metadata for a specific entity.getPropertyNames()
argument of entityname
renamed to entity
to allow not only for a name but an actual entity as well.getTableName()
argument of entityname
renamed to entity
to allow not only for a name but an actual entity as well.getKey()
argument of entityname
renamed to entity
to allow not only for a name but an actual entity as well.getEntityMetadata()
deleteByQuery()
reworked entirely to do native bulk delete queries. It now also returns the number of records removeddeleteWhere()
missing flush argument, added datasource as wellwirebox
: a WireBox reference already injected, logger
: a prepared logger for the class, datasource
The default datasource or constructed datasource for the class.debug
level, even for dynamic methods.options
that can have the following keys now:{
ignoreCase : boolean (false)
maxResults : numeric (0)
offset : numeric (0)
cacheable : boolean (false)
cacheName : string (default)
timeout : numeric (0)
datasource : string (defaults)
sortBy : hql to sort by,
autoCast : boolean (true),
asStream : boolean (false)
}
results = ormservice.findByLastLoginBetween( "User", "01/01/2008", "11/01/2008", { sortBy="LastName" } );
autocast:false
in the options to the calls.#Remember this entity extends Base Service, so we get all the features above plus the following:
Remember this entity extends the Virtual Service, so we get all the features above plus the following:
refresh(), merge(), evict()
refactored to encapsulate login in the base orm service and not itselfgetKey()
return typing to allow composite keys: https://github.com/coldbox-modules/cborm/pull/21cbvalidation
to v1.1.0StringBuffer
with StringBuilder
for performance
$
box install cborm