Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 120 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

v2.x

Loading...

Intro

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Getting Started

Loading...

Loading...

Loading...

Loading...

Base ORM Service

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Virtual Services

Loading...

Loading...

Loading...

Active Record

Loading...

Loading...

Loading...

Loading...

Criteria Queries

Loading...

Loading...

Loading...

Release History

In this section you will find the release notes for each version we release under this major version. If you are looking for the release notes of previous major versions use the version switcher at the top left of this documentation book. Here is a breakdown of our major version releases.

Version 2.0

A complete rewrite of the module to support a more modern and fluent approach to working with Hibernate/ColdFusion ORM. In this release we had to support 3 versions of Hibernate: 3 (Lucee), 4 (ACF 2016) and 5 (ACF 2018), which in itself proved to be a gargantuan task.

We also focused on bringing more functional programming aspects to working with collections of entities and even introduced cbStreams as part of the cborm module. This gives you the ability to produce streams out of any method that produces a collection of entities.

We also focused on converting the state of an object graph to a raw ColdFusion data struct as we live in the world of APIs. We include the mementifier module which allows every single entity to have a getMemento() method that will convert itself and its relationships to raw CF data constructs so you can take that state and either marshall it to another format (json,xml,excel) or audit the state.

Version 1.0

The first version of the cbORM series that focused on expanding the native ColdFusion ORM methods and exposing much more Hibernate functionality to the CFML world.

What's New With 2.5.0

Release Notes

  • Features : Introduction of the automatic resource handler for ORM Entities based on ColdBox's 6 resources and RestHandler

  • Improvement : 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 cfformat

You can read more about the :

RESTFul Resources here

What's New With 2.4.0

Release Notes

  • Feature : Upgraded to cbValidation 2.0.0

  • Feature : Updated the unique validator to match 2.0.0 standards

  • Feature : Upgraded to mementifier 2.0.0

What's New With 2.2.1

Release Notes

  • bug : virtual entity service still had entity required for casting methods

Counters

These methods allow you to do counting on entities with or without filtering.

Deleting Entities

These methods allow you to delete one or more entities in cascade style or bulk styles.

Criteria Queries

These methods allows you to tap into the Criteria Queries API so you can do fluent and functional queries with your ORM objects.

Finders

Finders are convenience methods that will help you find a single entity or a collection of entities by using criterias. If you want to use primary keys, then use the getters.

Utility Methods

Getters

Here is a collection of useful getter methods for entities by primary identifiers. Getters mostly deal with retrieving entities by primary keys instead of finders which rely on criteria operations. You can use the get() for a single entity, getOrFail() for throwing exceptions if an entity is not found and getAll() for multiple entities.

Creation - Population

These methods allow you to create entities and populate them from external data like structures, json, xml, queries and much more.

ORM Session

Introduction

The cborm module is a module that will enhance your experience when working with the ColdFusion 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.

Some Features

  • Service Layers with all the methods you could probably think off to help you get started in any project

  • Virtual service layers so you can create virtual services for any entity in your application

  • Automatic RESTFul resources handler, focus on your domain objects and business logic, not the boilerplate of REST

  • ActiveEntity our implementation of Active Record for ORM

  • Fluent queries via Hibernate's criteria and detached criteria queries with some Dynamic CFML goodness

  • Automatic transaction demarcation for save and delete operations

  • Dynamic finders and counters for expressive and fluent shorthand SQL

  • Automatic Java casting

  • Entity population from json, structs, xml, and queryies including building up their relationships

  • Entity validation via

  • Includes the to produce memento states from any entity, great for producing JSON

  • Ability for finders and queries to be returned as Java streams using our project.

In other words, it makes using an ORM not SUCK!

Versioning

The ColdBox ORM Module is maintained under the guidelines as much as possible.Releases will be numbered with the following format:

And constructed with the following guidelines:

  • Breaking backward compatibility bumps the major (and resets the minor and patch)

  • New additions without breaking backward compatibility bumps the minor (and resets the patch)

  • Bug fixes and misc changes bumps the patch

License

Apache 2 License:

Important Links

  • Code:

  • Issues:

Professional Open Source

The ColdBox ORM Module is a professional open source software backed by offering services like:

  • Custom Development

  • Professional Support & Mentoring

  • Training

  • Server Tuning

HONOR GOES TO GOD ABOVE ALL

Because of His grace, this project exists. If you don't like this, then don't read it, it's 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." Romans 5:5

What's New With 2.1.0

  • Change populate() in ActiveEntity so the target is the last argument so you can just pass a struct as the first argument

  • Make the save() operation return the saved entity or array of entities instead of the Base ORM service to allow for concatenated fluent design

Automatic Java Types

Most of the Hibernate extensions like criteria builders and even some dynamic finders and counters will have to rely on the underlying Java types in order to work. You do this in ColdFusion by using the javaCast() function available to you. So if you are using a primary key that is an Integer you might have to do the following in order to match your variable to the underlying Java type:

If you do not type it, then ColdFusion assumes it is a string and passes a string to Hibernate which will throw an exception as it is supposed to be an integer.

Auto Types

We have created two methods available to you in the base orm service, virtual service, criteria builders, active entity, etc to help you with these translations by automatically casting the values for you:

Method Expressions

A method expression is made up of the prefixes: findBy, findAllBy, countBy followed by the expression that combines a query upon one or more properties:

If a conditional keyword is not passed, we assume you want equality. Remember that!

IMPORTANT: The ? means that you can concatenate the same pattern over and over again.

getEntityMetadata

This method will return to you the hibernate's metadata for a specific entity.

Returns

  • The Hibernate Java ClassMetadata Object ()

getKeyValue

Get the unique identifier value for the passed in entity, or null if the instance is not in session

Returns

  • This function returns any

#29
#28

What's New With 2.3.0

Release Notes

  • improvement : 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 tests

Entity Convenience Methods

These collection of methods will give you information about the currently loaded entity or the entity class itself.

Querying

These methods are used for doing a-la-carte querying of entities with extra pizzaz!

Saving Entities

Conditionals

The available conditionals in ColdBox are:

  • LessThanEquals - Less than or equal to passed value

  • LessThan - Less than to passed value

  • GreaterThanEquals - Greater than or equal to passed value

  • GreaterThan - Greater than to passed value

  • Like - Equivalent to the SQL like expression

  • NotEqual - Not equal to the passed value

  • isNull - The property must be null

  • isNotNull - The property must not be null

  • NotBetween - The property value must not be between two values

  • Between - The property value must be between two values

  • NotInList - The property value must not be in the passed in simple list or array

  • inList - The property value must be in the passed in simple list or array

Operators

The only valid operators are:

  • And

  • Or

Usage

Now that you have created your entities, how do we use them? Well, you will be using them from your handlers or other services by leveraging WireBox's getInstance() method. You can use entityNew() as well, but if you do, you will loose any initial dependency injection within the entity. This is a ColdFusion limitation where we can't listen for new entity constructions. If you want to leverage DI, as best practice retrieve everything from WireBox.

Once you have an instance of the entity, then you can use it to satisfy your requirements with the entire gamut of functions available from the base services.

Tip: You can also check out our Basic CRUD guide for an initial overview of the usage.

component{
    
    function index( event, rc, prc ){
        prc.data = getInstance( "User" ).list( sortOrder="fname" );
        prc.stream = getInstance( "User" ).list( sortOrder="fname", asStream=true );
    }
    
    function count( event, rc, prc ){
        return getInstance( "User" ).count()
        return getInstance( "User" ).countWhere( { isActive : true } );
    }
    
    function show( event, rc, prc ){
        return getInstance( "User" )
            .getOrFail( 123 )
            .getMemento();
    }
    
    function save( event, rc, prc ){
        return populateModel( model="User", composeRelationships=true )
            .save()
            .getMemento();
    }
    
    function delete( event, rc, prc ){
        getInstance( "User" )
            .getOrFail( rc.id ?: -1 )
            .delete();
        return "User Deleted";
    }

}

nullValue()

Produce a null value that can be used anywhere you like!

autoCast( entity, propertyName, value )

This method allows you to cast any value to the appropriate type in Java for the property passed in. The entity argument can be the entity name or an entity object.

idCast( entity, id )

This method allows you to cast the identifier value to the appropriate type in Java. The entity argument can be the entity name or an entity object.

Example

So instead of casting it manually you can just let us do the work:

Arguments

Key

Type

Required

Default

Description

entity

any

Yes

---

The entity name or entity object

Examples

https://docs.jboss.org/hibernate/orm/3.5/javadocs/org/hibernate/metadata/ClassMetadata.html
Arguments

Key

Type

Required

Default

Description

entity

string

Yes

---

The entity to inspect for it's id

Examples

var pkValue = ormService.getKeyValue( "User" );
User.findBy{property}[Conditional=equal][Operator]?{property}[Conditional][Operator]
User.findAllBy{property}[Conditional=equal][Operator]?{property}[Conditional][Operator]
User.countBy{property}[Conditional=equal][Operator]?{property}[Conditional][Operator]
criteria.eq( "id", javaCast( "int", arguments.id ) );
criteria.eq( "id", criteria.idCast( arguments.id ) );
var md = ORMService.getEntityMetadata( entity );

Security Hardening

  • Code Reviews

  • Much More

  • cbValidation
    Mementifier project
    cbStreams
    Semantic Versioning
    http://www.apache.org/licenses/LICENSE-2.0
    https://github.com/coldbox-modules/cbox-cborm
    https://github.com/coldbox-modules/cbox-cborm/issues
    Ortus Solutions, Corp
    Ortus Solutions, Corp

    About This Book

    The source code for this book is hosted in GitHub: https://github.com/ortus-docs/cbox-cborm-docs. You can freely contribute to it and submit pull requests. The contents of this book is copyright by Ortus Solutions, Corp and cannot be altered or reproduced without author's consent. All content is provided "As-Is" and can be freely distributed.

    • The majority of code examples in this book are done in cfscript.

    • The majority of code generation and running of examples are done via CommandBox: The ColdFusion (CFML) CLI, Package Manager, REPL - https://www.ortussolutions.com/products/commandbox

    External Trademarks & Copyrights

    Flash, Flex, ColdFusion, and Adobe are registered trademarks and copyrights of Adobe Systems, Inc.

    Notice of Liability

    The information in this book is distributed “as is”, without warranty. The author and Ortus Solutions, Corp shall not have any liability to any person or entity with respect to loss or damage caused or alleged to be caused directly or indirectly by the content of this training book, software and resources described in it.

    Contributing

    We highly encourage contribution to this book and our open source software. The source code for this book can be found in our where you can submit pull requests.

    Charitable Proceeds

    10% of the proceeds of this book will go to charity to support orphaned kids in El Salvador - . So please donate and purchase the printed version of this book, every book sold can help a child for almost 2 months.

    Shalom Children's Home

    Shalom Children’s Home is one of the ministries that is dear to our hearts located in El Salvador. During the 12 year civil war that ended in 1990, many children were left orphaned or abandoned by parents who fled El Salvador. The Benners saw the need to help these children and received 13 children in 1982. Little by little, more children came on their own, churches and the government brought children to them for care, and the Shalom Children’s Home was founded.

    Shalom now cares for over 80 children in El Salvador, from newborns to 18 years old. They receive shelter, clothing, food, medical care, education and life skills training in a Christian environment. The home is supported by a child sponsorship program.

    We have personally supported Shalom for over 6 years now; it is a place of blessing for many children in El Salvador that either have no families or have been abandoned. This is good earth to seed and plant.

    Author

    Luis Fernando Majano Lainez

    Luis Majano is a Computer Engineer that has been developing and designing software systems since the year 2000. He was born in San Salvador, El Salvador in the late 70’s, during a period of economical instability and civil war. He lived in El Salvador until 1995 and then moved to Miami, Florida where he completed his Bachelors of Science in Computer Engineering at Florida International University. Luis resides in Houston, Texas with his beautiful wife Veronica, baby girl Alexia and baby boy Lucas!

    He is the CEO of Ortus Solutions, a consulting firm specializing in web development, ColdFusion (CFML), Java development and all open source professional services under the ColdBox and ContentBox stack. He is the creator of ColdBox, ContentBox, WireBox, MockBox, LogBox and anything “BOX”, and contributes to many open source ColdFusion/Java projects. You can read his blog at

    Luis has a passion for Jesus, tennis, golf, volleyball and anything electronic. Random Author Facts:

    • He played volleyball in the Salvadorean National Team at the tender age of 17

    • The Lord of the Rings and The Hobbit is something he reads every 5 years. (Geek!)

    • His first ever computer was a Texas Instrument TI-86 that his parents gave him in 1986. After some time digesting his very first BASIC book, he had written his own tic-tac-toe game at the age of 9. (Extra geek!)

    Keep Jesus number one in your life and in your heart. I did and it changed my life from desolation, defeat and failure to an abundant life full of love, thankfulness, joy and overwhelming peace. As this world breathes failure and fear upon any life, Jesus brings power, love and a sound mind to everybody!

    “Trust in the LORD with all your heart, and do not lean on your own understanding.” Proverbs 3:5

    Contributors

    Will de Bruin

    Brad Wood

    Query Options

    If you pass a structure as the last argument to your dynamic finder/counter call, we will consider that by convention to be your query options.

    user = entityNew( "User" )
        .findByLastName( "Majano", { ignoreCase=true, timeout=20 } );
    
    users = entityNew( "User" )
        .findAllByLastNameLike( "Ma%", { ignoreCase=false, max=20, offset=15 } );

    The valid query options are:

    • ignorecase : Ignores the case of sort order when you set it to true.

    • maxResults : Specifies the maximum number of objects to be retrieved.

    • offset : Specifies the start index of the resultset from where it has to start the retrieval.

    • cacheable : Whether the result of this query is to be cached in the secondary cache. Default is false.

    • cachename : Name of the cache in secondary cache.

    • timeout : Specifies the timeout value (in seconds) for the query

    • datasource : The datasource to use, it defaults to the application

    • sortBy : The HQL to sort the query by

    • autoCast : No more casting, let us do auto casting for you

    • asStream : Want a stream back instead of the results, no problem!

    Here is a more descriptive key set with the types and defaults

    Service Methods

    The Base ORM Service has a ton of methods to assist you with your ORM needs. We have compiled them under this section under several different categories:

    • Criteria Queries

    • Creation - Population

    • Counters

    • Deleting Entities

    • Entity Convenience

    • Finders

    • Getters

    • ORM Session

    • Querying

    • Saving

    • Utility

    Like always, you can find the latest in the link below:

    exists

    Checks if the given entityName and id exists in the database, this method does not load the entity into session. A very useful approach to check for data existence.

    Returns

    • This function returns boolean

    Arguments

    Examples

    getRestrictions

    Get our hibernate org.hibernate.criterion.Restrictions proxy object that will help you produce criterias.

    Returns

    • This function returns an instance of cborm.models.criterion.Restrictions

    Examples

    Adobe ColdFusion will throw an "Invalid CFML construct" for certain CBORM methods that match , such as .and(), .or(), and .eq(). To avoid these errors and build cross-engine compatible code, use .$and(), .$or(), and .isEq().

    getTableName

    Returns the table name of the passed in entity

    Returns

    • This function returns string

    Arguments

    Examples

    findWhere

    Find one entity (or null if not found) according to a criteria structure ex: findWhere(entityName="Category", {category="Training"}), findWhere(entityName="Users",{age=40,retired=false});

    Returns

    • This function returns any

    Arguments

    Examples

    getSessionStatistics

    Information about the first-level (session) cache for the current session

    Returns

    • This function returns struct

    Arguments

    Examples

    evictQueries

    Evict all queries in the default cache or the cache region that is passed in.

    Returns

    • This function returns void

    Arguments

    Examples

    findByExample

    Find all/single entities by example

    Returns

    • This function returns array

    Arguments

    Examples

    isDirty

    Verifies if the passed in entity has dirty values or not since loaded from the database.

    Returns

    • This function returns true if the entity values have been changed since loaded into the hibernate session, else false

    Arguments

    Examples

    getPropertyNames

    Returns the persisted Property Names of the entity in array format

    Returns

    • This function returns array

    Arguments

    Examples

    countWhere

    Returns the count by passing name value pairs as arguments to this function. One mandatory argument is to pass the 'entityName'. The rest of the arguments are used in the where class using AND notation and parameterized. Ex: countWhere(entityName="User",age="20");

    Returns

    • This function returns numeric

    Arguments

    Examples

    sessionContains

    Checks if the current hibernate session contains the passed in entity.

    Returns

    • This function returns boolean

    Arguments

    Examples

    isSessionDirty

    Checks if the session contains dirty objects that are awaiting persistence

    Returns

    • This function returns boolean

    Arguments

    Examples

    getKey

    Returns the key (id field) of a given entity, either simple or composite keys.

    • If the key is a simple pk then it will return a string, if it is a composite key then it returns an array.

    • If the key cannot be identified then a blank string is returned.

    Returns

    • This function returns any

    Arguments

    Examples

    clear

    Clear the session removes all the entities that are loaded or created in the session. This clears the first level cache and removes the objects that are not yet saved to the database.

    Returns

    • This function returns the base service reference (this)

    Arguments

    Examples

    getEntityGivenName

    Returns the entity name from a given entity object via session lookup or if new object via metadata lookup

    Returns

    • This function returns string

    newCriteria

    Get a brand new criteria builder object to do criteria queries with (See )

    Returns

    • This function returns coldbox.system.orm.hibernate.CriteriaBuilder

    Method Signatures

    We have three types of dynamic finders and counters:

    • findBy : Find ONE entity according to method signature, if more than one record is found an exception is thrown

    • findAllBy : Find ALL entities according to method signature

    refresh

    Refresh the state of an entity or array of entities from the database

    Returns

    • This function returns back the BaseORMService (this)

    deleteWhere

    Deletes entities by using name value pairs as arguments to this function. One mandatory argument is to pass the 'entityName'. The rest of the arguments are used in the where class using AND notation and parameterized. Ex: deleteWhere(entityName="User",age="4",isActive=true);

    Returns

    • This function returns numeric

    merge

    Merge an entity or array of entities back into the session

    Returns

    • Same entity if one passed, array if an array of entities passed.

    saveAll

    Saves an array of passed entities in specified order and transaction safe

    Returns

    • This function returns void

    getDirtyPropertyNames

    Get an array of property names that that have been changed from the original loaded session state.

    Returns

    • An array of property names

    save

    Save an entity using hibernate transactions or not. You can optionally flush the session also.

    Returns

    • This function returns void

    count

    Return the count of instances in the DB for the given entity name. You can also pass an optional where statement that can filter the count. Ex: count('User','age > 40 AND name="joe"'). You can even use named or positional parameters with this method: Ex: count('User','age > ? AND name = ?',[40,"joe"])

    Returns

    • This function returns numeric

    Dynamic Finders- Counters

    The ORM module supports the concept of dynamic finders and counters for ColdFusion ORM entities. A dynamic finder/counter looks like a real method but it is a virtual method that is intercepted by via onMissingMethod. This is a great way for you to do finders and counters using a programmatic and visual representation of what HQL to run.

    This feature works on the Base ORM Service, Virtual Entity Services and also Active Entity services. The most semantic and clear representations occur in the Virtual Entity Service and Active Entity as you don't have to pass an entity name around.

    Automatic Casting

    Another important aspect of the dynamic finders is that we will AUTO CAST all the values for you. So you don't have to mess with the right Java type, we will do it for you.

    autoCast

    This method allows you to cast any value to the appropriate type in Java for the property passed in. The entity argument can be the entity name or an entity object.

    Returns

    • This function returns the value casted to the right Java type

    evict

    Evict entity object(s) from the hibernate session or first-level cache.

    • An entity object

    • An array of entity objects

    getOrFail

    Get an entity using a primary key, if the id is not found this method throws an EntityNotFound Exception

    No casting is necessary on the Id value type as we do this automatically for you.

    idCast

    This method allows you to cast the identifier value to the appropriate type in Java. The entity argument can be the entity name or an entity object. Please note that this is ONLY used for identifier casting not for any property!

    Returns

    • This function returns the value casted to the right Java type

    Installation

    Leverage CommandBox to install into your ColdBox app:

    System Requirements

    • Lucee 5.x+

    nullValue

    Produce a null value that can be used anywhere you like!

    Returns

    • A null value

    # A quick preview of some functionality
    
    var book = new Book().findByTitle( "My Awesome Book" );
    var book = new Book().getOrFail( 2 );
    new Book().getOrFail( 4 ).delete();
    new Book().deleteWhere( isActive:false, isPublished:false );
    
    property name="userService" inject="entityService:User";
    
    return userService.list();
    return userService.list( asStream=true );
    
    var count = userService.countWhere( age:20, isActive:true );
    var users = userService.findAllByLastLoginBetween( "01/01/2019", "05/01/2019" );
    
    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();
    <major>.<minor>.<patch>
    {
        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)
    }

    Key

    Type

    Required

    Default

    Description

    entityName

    any

    Yes

    ---

    id

    any

    Yes

    ---

    // Get an instance of the restrictions
    var restrictions = ormService.getRestrictions();
    
    // Restrictions used with criteria queries
    var r = ormService.getRestrictions();
    var users = ormService.newCriteria("User")
        .or( r.eq("lastName","majano"), r.gt("createDate", now()) )
        .list();
    reserved operator names

    Key

    Type

    Required

    Default

    Description

    entity

    string

    Yes

    ---

    var persistedTable = ormService.getTableName( "Category" );

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    Yes

    ---

    criteria

    struct

    Yes

    ---

    A structure of criteria to filter on

    Key

    Type

    Required

    Default

    Description

    datasource

    string

    false

    ---

    The default or specific datasource use

    Key

    Type

    Required

    Default

    Description

    cacheName

    string

    No

    ---

    datasource

    string

    No

    ---

    The datasource to use

    Key

    Type

    Required

    Default

    Description

    example

    any

    Yes

    ---

    The entity sample

    unique

    boolean

    false

    false

    Return an array of sample data or none

    Key

    Type

    Required

    Default

    Description

    entity

    Any

    Yes

    ---

    The entity object

    if( ormService.isDirty( entity ) ){
        // The values have been modified
        log.debug( "entity values modified", ormService.getDirtyPropertyNames( entity ) );
    }

    Key

    Type

    Required

    Default

    Description

    entity

    string

    Yes

    ---

    var properties = ormService.getPropertyNames("User");

    Key

    Type

    Required

    Default

    entityName

    string

    Yes

    ---

    countWhere( entityName="User", age="20" );

    Key

    Type

    Required

    Default

    Description

    entity

    any

    Yes

    ---

    Key

    Type

    Required

    Default

    Description

    datasource

    string

    false

    ---

    The default or specific datasource to use

    // Check if by this point we have a dirty session, then flush it
    if( ormService.isSessionDirty() ){
      ORMFlush();
    }

    Key

    Type

    Required

    Default

    Description

    entity

    string

    Yes

    ---

    The entity name or entity object

    Key

    Type

    Required

    Default

    Description

    datasource

    string

    false

    ---

    The default or specific datasource use

    ormService.clear();
    Arguments

    Key

    Type

    Required

    Default

    Description

    entity

    any

    Yes

    ---

    Examples

    var name = ORMService.getEntityGivenName( entity );
    countBy : Give you a count of entities according to method signature

    Let's say you have the following entity:

    Then we could do the following:

    You can also use the virtual entity service instead of active entity.

    If you just use a vanilla Base ORM Service, then the first argument must be the entityName:

    component persistent="true" name="User" extends="cborm.models.ActiveEntity"{
    
         property name="id" column="user_id" fieldType="id" generator="uuid";
         property name="lastName";
         property name="userName";
         property name="password";
         property name="lastLogin" ormtype="date";
    }
    user = getInstance( "User" ).findByLastName( "Majano" );
    
    users = getInstance( "User" ).findAllByLastNameLike( "Ma%" );
    
    users = getInstance( "User" ).findAllByLastLoginBetween( "01/01/2010", "01/01/2012" );
    
    users = getInstance( "User" ).findAllByLastLoginGreaterThan( "01/01/2010" );
    
    users = getInstance( "User" ).findAllByLastLoginGreaterThanAndLastNameLike( "01/01/2010", "jo%" );
    
    count = getInstance( "User" ).countByLastLoginGreaterThan( "01/01/2010" );
    
    count = getInstance( "User" ).countByLastLoginGreaterThanAndLastNameLike( "01/01/2010", "jo%" );
    Arguments

    Key

    Type

    Required

    Default

    Description

    entity

    any

    Yes

    ---

    Examples

    var user = storage.getVar("UserSession");
    ormService.refresh( user );
    
    var users = [user1,user2,user3];
    ormService.refresh( users );

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    Yes

    ---

    transactional

    boolean

    No

    From Property

    Use transactions not

    Examples

    Arguments

    Key

    Type

    Required

    Default

    Description

    entity

    any

    Yes

    ---

    Examples

    // merge a single entity back
    ormService.merge( userEntity );
    // merge an array of entities
    collection = [entity1,entity2,entity3];
    ormService.merge( collection );
    Arguments

    Key

    Type

    Required

    Default

    Description

    entity

    Any

    Yes

    ---

    The entity object

    Examples

    if( ormService.isDirty( entity ) ){
        // The values have been modified
        log.debug( "entity values modified", ormService.getDirtyPropertyNames( entity ) );
    }

    Streams

    We have also enabled the ability to return a stream of objects if you are using the findAll semantics via cbStreams.

    users = getInstance( "User" )
        .findAllByLastLoginBetweeninGreaterThan( "01/01/2010" );
    
    users = getInstance( "User" )
        .findAllByLastLoginGreaterThanAndLastNameLike( "01/01/2010", "jo%" );
    
    count = getInstance( "User" )
        .countByLastLoginGreaterThan( "01/01/2010" );
    
    count = getInstance( "User" )
        .countByLastLoginGreaterThanAndLastNameLike( "01/01/2010", "jo%" );
    userStream = getInstance( "User" )
        .findAllByLastLoginBetweeninGreaterThan( "01/01/2010", {asStream:true} );
    Returns
    • This function returns void

    Arguments

    Key

    Type

    Required

    Description

    entities

    any

    Yes

    The argument can be one persistence entity or an array of entities to evict

    Examples

    ormService.evict( thisEntity );
    ColdFusion 2016+

    Application.cfc Setup

    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:

    WireBox DSL

    The module registers a new WireBox DSL called entityservice which can produce virtual or base orm entity services. Below are the injections you can use:

    • entityservice - Inject a global ORM service

    • entityservice:{entityName} - Inject a Virtual entity service according to entityName

    Module Settings

    Here are the module settings you can place in your ColdBox.cfc under moduleSettings -> cborm structure:

    Validation

    We have also integrated a UniqueValidator from the validation module into our ORM module. It is mapped into WireBox as UniqueValidator@cborm so you can use in your model constraints like so:

    Examples
    baseService.nullValue()
    if( ormService.exists("Account",123) ){
     // do something
    }
    // Find a category according to the named value pairs I pass into this method
    var category = ormService.findWhere(entityName="Category", criteria={isActive=true, label="Training"});
    var user = ormService.findWhere(entityName="User", criteria={isActive=true, username=rc.username,password=rc.password});
    // Let's get the session statistics
    stats = ormService.getSessionStatistics;
    
    // Lets output it
    <cfoutput>
    collection count: #stats.collectionCount# <br/>
    collection keys: #stats.collectionKeys# <br/>
    entity count: #stats.entityCount# <br/>
    entity keys: #stats.entityKeys#
    </cfoutput>
    // evict queries that are in the default hibernate cache
    ormService.evictQueries();
    // evict queries for this service
    ormService.evictQueries( ormService.getQueryCacheRegion() );
    // evict queries for my artists
    ormService.evictQueries( "MyArtits" );
    currentUser = ormService.findByExample( session.user, true );
    function checkSomething( any User ){
      // check if User is already in session
      if( NOT ormService.sessionContains( arguments.User ) ){
         // Not in hibernate session, so merge it in.
         ormService.merge( arguments.User );
      }
    }
    var pkField = ormService.getKey( "User" );
    // Get a virtual entity service via DI, there are many ways to get a virtual entity service
    // Look at virtual entity service docs for retrieval
    property name="userService" inject="entityservice:userService";
    
    user = userService.findByLastName( "Majano" );
    
    users = userService.findAllByLastNameLike( "Ma%" );
    
    users = userService.findAllByLastLoginBetween( "01/01/2010", "01/01/2012" );
    
    users = userService.findAllByLastLoginGreaterThan( "01/01/2010" );
    
    users = userService.findAllByLastLoginGreaterThanAndLastNameLike( "01/01/2010", "jo%" );
    
    count = userService.countByLastLoginGreaterThan( "01/01/2010" );
    
    count = userService.countByLastLoginGreaterThanAndLastNameLike( "01/01/2010", "jo%" );
    // Get a virtual entity service via DI, there are many ways to get a base entity service
    // Look at base entity service docs for retrieval
    property name="userService" inject="entityservice";
    
    user = userService.findByLastName( "User", "Majano" );
    ormService.deleteWhere(entityName="User", isActive=true, age=10);
    ormService.deleteWhere(entityName="Account", id="40");
    ormService.deleteWhere(entityName="Book", isReleased=true, author="Luis Majano");
    # Latest version
    install cborm
    
    # Bleeding Edge
    install cborm@be
    Application.cfc
    # In the pseudo constructor
    this.mappings[ "/cborm" ] = COLDBOX_APP_ROOT_PATH & "modules/cborm";
    config/ColdBox.cfc
    moduleSettings = {
        cborm = {
            // Resource Settings
        		resources : {
        			// Enable the ORM Resource Event Loader
        			eventLoader 	: false,
        			// Pagination max rows
        			maxRows 		: 25,
        			// Pagination max row limit: 0 = no limit
        			maxRowsLimit 	: 500
        		},
            // WireBox Injection bridge
            injection = {
                // enable entity injection via WireBox
                enabled = true, 
                // Which entities to include in DI ONLY, if empty include all entities
                include = "", 
                // Which entities to exclude from DI, if empty, none are excluded
                exclude = ""
            }
        }
    }
    { fieldName : { validator: "UniqueValidator@cborm" } }
    He has a geek love for circuits, microcontrollers and overall embedded systems.
  • He has of late (during old age) become a fan of organic gardening.

  • www.luismajano.com
    Arguments

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    true

    ---

    The entity name to bind the criteria query to

    useQueryCaching

    boolean

    false

    false

    Examples

    ORM:CriteriaBuilder
    Arguments

    Key

    Type

    Required

    Default

    Description

    entities

    array

    Yes

    ---

    The array of entities to persist

    forceInsert

    boolean

    No

    false

    Examples

    Arguments

    Key

    Type

    Required

    Default

    Description

    entity

    any

    Yes

    ---

    The entity to save

    forceInsert

    boolean

    No

    false

    Examples

    Arguments

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    Yes

    ---

    where

    string

    No

    Examples

    Arguments

    Key

    Type

    Required

    Default

    Description

    entity

    string

    Yes

    The entity name or entity object

    propertyName

    string

    Yes

    Examples

    Returns
    • This function returns any

    Arguments

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    Yes

    ---

    id

    any

    Yes

    ---

    Examples

    Arguments

    Key

    Type

    Required

    Default

    Description

    entity

    string

    Yes

    The entity name or entity object

    id

    any

    Yes

    Examples

    GitHub repository
    https://www.harvesting.org/
    Shalom Children's Home
    API Docs

    new

    Get a new entity object by entity name. You can also pass in a structure called properties that will be used to populate the new entity with or you can use optional named parameters to call setters within the new entity to have shorthand population.

    Returns

    • This function returns the newly created entity

    Arguments

    Examples

    deleteAll

    Deletes all the entity records found in the database in a transaction safe matter and returns the number of records removed

    This method will respect cascading deletes if any

    Returns

    • This function returns numeric

    Arguments

    Examples

    delete

    Delete an entity using safe transactions. The entity argument can be a single entity or an array of entities. You can optionally flush the session also after committing.

    This method will respect cascading deletes if any

    Returns

    • This function returns void

    Arguments

    Examples

    findit

    Finds and returns the first result for the given query or null if no entity was found. You can either use the query and params combination or send in an example entity to find.

    Returns

    • This function returns any

    Arguments

    Examples

    get

    Get an entity using a primary key, if the id is not found this method returns null. You can also pass an id = 0 and the service will return to you a new entity.

    No casting is necessary on the Id value type as we do this automatically for you.

    Returns

    • This function returns any

    Arguments

    Examples

    evictCollection

    Evict all the collection or association data for a given entity name and collection name from the secondary cache ONLY, not the hibernate session.

    Evict an entity name with or without an ID from the secondary cache ONLY, not the hibernate session

    Returns

    • This function returns void

    Arguments

    Examples

    What's New With 2.2.0

    This release not only has some bug fixes but several new features that pack a punch.

    Major Features

    Criteria Query Fluent If Statements - when()

    Service Properties

    There are a few properties you can instantiate a base service with or set them afterwards that affect operation. Below you can see a nice chart for them:

    Concrete Services

    Let's say you are using the virtual services and base ORM service but you find that they do not complete your requirements, or you need some custom methods or change functionality. Then you will be building concrete services that inherit from the base or virtual entity services. This is the very purpose of these support classes as most of the time you will have custom requirements and your own style of coding.

    Here is a custom AuthorService we created:

    Then you can just inject your concrete service in your handlers, or other models like any other normal model object.

    populateFromXML

    Populate an entity with an XML packet. Make sure the names of the elements match the keys in the structure.

    Returns

    • This function returns the populated object

    findAllWhere

    Find all entities according to criteria structure. Ex: findAllWhere(entityName="Category", {category="Training"}), findAllWhere(entityName="Users", {age=40,retired=true});

    Returns

    • This function returns array

    Overview

    The BaseORMService is a core model CFC of the module that will provide you with a tremendous gammut of API methods to interact with ColdFusion ORM Entities.

    Concept

    The idea behind this support class is to provide a very good base or parent service layer that can interact with ColdFusion ORM via hibernate and entities inspired by support. This means that you don't need to create a service layer CFC in order to work with ORM entities.

    It provides tons of methods for query executions, paging, transactions, session metadata, caching and much more. You can either use the class on its own or create more concrete service layers by inheriting from this class.

    getAll

    Retrieve all the instances from the passed in entity name using the id argument if specified. The id can be a list of IDs or an array of IDs or none to retrieve all. If the id is not found or returns null the array position will have an empty string in it in the specified order

    You can use the readOnly argument to give you the entities as read only entities.

    You can use the properties argument so this method can return to you array of structs instead of array of objects. The property list must include the as alias if not you will get positional keys.

    Example Positional: properties="catID,category Example Aliases: properties="catID as id, category as category, role as role"

    You can use the

    deleteByID

    Delete using an entity name and an incoming id, you can also flush the session if needed. The ID can be a single ID or an array of ID's to batch delete using hibernate DLM style deletes. The function also returns the number of records deleted.

    No cascading will be done since the delete is done without loading the entity into session but via DLM HQL

    Constructor Properties

    There are a few properties you can instantiate the ActiveEntity with or set them afterwards that affect operation. Below you can see a nice chart for them:

    Criteria Builder

    Hibernate provides several ways to retrieve data from the database. We have seen the normal entity loading operations in our basic CRUD and we have seen several HQL and SQL query methods as well. The last one is the .

    The ColdBox Hibernate Criteria Builder is a powerful object that will help you build and execute in a fluent and dynamic manner. is extremely powerful, but some developers prefer to build queries dynamically using an object-oriented API, rather than building query strings and concatenating them in strings or buffers. This is error prone, syntax crazy and sometimes untestable.

    The ColdBox Criteria Builders offers a powerful programmatic DSL builder for Hibernate Criteria queries. It focuses on a criteria object that you will build up to represent the query to execute. The cool thing is that you can even retrieve the exact HQL or even SQL the criteria query will be executing. You can get the explain plans, provide query hints and much more. In our experience, criteria queries will make your life much easier when doing complicated queries.

    deleteByQuery

    Delete by using an HQL query and iterating via the results, it is not performing a delete query but it actually is a select query that should retrieve objects to remove

    Returns

    • This function returns void

    var users = ORMService.newCriteria( entityName="User" )
        .gt( "age", ormService.idCast( 30 ) )
        .isTrue( "isActive" )
        .list( max=30, offset=10, sortOrder="lname" );
    var user = ormService.new("User");
    populateModel(user);
    var user2 = ormService.new("User");
    populateModel(user);
    
    ormService.saveAll( [user1,user2] );
    var user = ormService.new("User");
    populateModel(user);
    ormService.save(user);
    
    // Save with immediate flush
    var user = ormService.new(entityName="User", lastName="Majano");
    ormService.save(entity=user, flush=true);
    // Get the count of instances for all books
    ormService.count("Book");
    // Get the count for users with age above 40 and named Bob
    ormService.count("User","age > 40 AND name='Bob'");
    // Get the count for users with passed in positional parameters
    ormService.count("User","age > ? AND name=?",[40,'Bob']);
    // Get the count for users with passed in named parameters
    ormService.count("Post","title like :title and year = :year",{title="coldbox",year="2007"});
    baseService.autoCast( "User", "createdDate", arguments.myDate );
    var account = ormService.getOrFail("Account",1);
    var account = ormService.getOrFail("Account",4);
    
    var newAccount = ormService.getOrFail("Account",0);
    baseService.idCast( "User", "123" );

    Do automatic query caching for queries

    queryCacheRegion

    string

    false

    criterias.{entityName}

    The queryCacheRegion name property for all queries in this criteria object

    datasource

    string

    false

    The datasource to use or default it to the application or entity in use

    flush

    boolean

    No

    false

    transactional

    boolean

    No

    true

    Use ColdFusion transactions or not

    Insert as new record whether it already exists or not

    flush

    boolean

    No

    false

    Do a flush after saving the entity, false by default since we use transactions

    transactional

    boolean

    No

    true

    Wrap the save in a ColdFusion transaction

    params

    any

    No

    strucnew()

    Named or positional parameters

    The property name

    value

    any

    Yes

    The property value

    The value to cast

    composeRelationships

    boolean

    false

    true

    Automatically attempt to compose relationships from the incoming properties memento

    nullEmptyInclude

    string

    false

    ---

    A list of keys to NULL when empty

    nullEmptyExclude

    string

    false

    ---

    A list of keys to NOT NULL when empty

    ignoreEmpty

    boolean

    false

    false

    Ignore empty property values on populations

    include

    string

    false

    ---

    A list of keys to include in the population from the incoming properties memento

    exclude

    string

    false

    ---

    A list of keys to exclude in the population from the incoming properties memento

    Key

    Type

    Required

    Default

    Description

    entityName

    any

    true

    ---

    properties

    struct

    false

    {}

    A structure of name-value pairs to populate the new entity with

    transactional

    boolean

    No

    From Property

    Use transactions or not

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    Yes

    ---

    The entity to purge

    flush

    boolean

    No

    false

    transactional

    boolean

    No

    From Property

    Use Transactions or not

    Key

    type

    Required

    Default

    Description

    entity

    any

    Yes

    ---

    flush

    boolean

    No

    false

    timeout

    numeric

    No

    0

    ignoreCase

    boolean

    No

    false

    datasource

    string

    No

    Key

    Type

    Required

    Default

    Description

    query

    string

    No

    ---

    The HQL Query to execute

    params

    any

    No

    {}

    Positional or named params

    returnNew

    boolean

    false

    true

    If id is 0 or empty and this is true, then a new entity is returned.

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    Yes

    ---

    id

    any

    Yes

    ---

    id

    any

    false

    The id to use for eviction according to entity name or relation name

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    Yes

    ---

    The entity name to evict or use in the eviction process

    relationName

    string

    false

    The name of the relation in the entity to evict

    How many times have you been dealing with if statements in order to add some restrictions into your criteria object? Many, this was the only way before, not anymore. So instead of doing something like the following:

    This looks like normal code, but we can do a more functional approach by introducing the when() function:

    This function takes in as the first argument a boolean value, if the value is true, then the target closure will be called for you and the criteria will be passed via the arguments scope:

    This construct will help you create more fluent designs when building criteria queries, enjoy!

    PeekaBoo! - peek()

    We have also enhanced the criteria queries with a peek() function which allows you to peek in the current position of the criteria build up. This allows you to debug or inspect the SQL/HQL inside the criteria at that point in time. You can use it for sending debug data or logging, or auditing.

    Enjoy your peekaboo function!

    ValidateOrFail()

    We have added a new function on the ActiveEntity object to assist with validations. The validateOrFail() function will allow you to validate the entity and if it validates it just returns the instance of the entity for a nice fluent design. However, if the validation fails, it throws a ValidationException and the errors are passed to the exception object via the extendedInfo key. You can then deal with the exception as needed.

    Release Notes

    • Features: New function for criteria query when( boolean, target ) that you can use to build functional criterias without the use of if statements.

    • 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

    boolean

    false

    false

    To enable the caching of queries used by this base service

    eventHandling

    boolean

    false

    true

    Announce interception events on new() operations and save() operations: ORMPostNew, ORMPreSave, ORMPostSave

    useTransactions

    boolean

    false

    true

    Enables ColdFusion safe transactions around all operations that either save, delete or update ORM entities

    defaultAsQuery

    boolean

    false

    true

    The bit that determines the default return value for list() and executeQuery() as query or array of objects

    datasource

    string

    false

    System Default

    The default datasource to use for all transactions. If not set, we default it to the system datasource or the one declared in the persistent CFC.

    So if I was to base off my services on top of the Base Service, I can do this:

    Property

    Type

    Required

    Default

    Description

    queryCacheRegion

    string

    false

    ORMService.defaultCache

    The name of the secondary cache region to use when doing queries via this base service

    useQueryCaching

    useQueryCaching

    boolean

    false

    false

    To enable the caching of queries used by this entity

    eventHandling

    boolean

    false

    true

    Announce interception events on new() operations and save() operations: ORMPostNew, ORMPreSave, ORMPostSave

    useTransactions

    boolean

    false

    true

    Enables ColdFusion safe transactions around all operations that either save, delete or update ORM entities

    defaultAsQuery

    boolean

    false

    true

    The bit that determines the default return value for list(), executeQuery() as query or array of objects

    Here is a nice example of calling the super.init() class with some of these constructor properties.

    Property

    Type

    Required

    Default

    Description

    queryCacheRegion

    string

    false

    #entityName#.activeEntityCache

    The name of the secondary cache region to use when doing queries via this entity

    // return empty post entity
    var post = ormService.new("Post");
    var user = ormService.new(entityName="User",properties={firstName="Luis", lastName="Majano", age="32", awesome=true});
    var user = ormService.new("User",{fname="Luis",lname="Majano",cool=false,awesome=true});
    ormService.deleteAll("Tags");
    var post = ormService.get(1);
    ormService.delete( post );
    
    // Delete a flush immediately
    ormService.delete( post, true );
    // My First Post
    ormService.findIt("from Post as p where p.author='Luis Majano'");
    // With positional parameters
    ormService.findIt("from Post as p where p.author=?", ["Luis Majano"]);
    // with a named parameter (since 0.5)
    ormService.findIt("from Post as p where p.author=:author and p.isActive=:active", { author="Luis Majano",active=true} );
    var account = ormService.get("Account",1);
    var account = ormService.get("Account",4);
    
    var newAccount = ormService.get("Account",0);
    c = newCriteria();
    
    if( isBoolean( arguments.isPublished ) ){
      
    	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","" );
        }
    
    }
    
    if( !isNull( arguments.showInSearch ) ){
    	c.isEq( "showInSearch", showInSearch );
    }
    
    
    return c.list();
    /**
    * @test The boolean evaluation
    * @target The closure to execute if test is true
    */
    when( boolean test, function target )
    newCriteria()
        .when( isBoolean( arguments.isPublished ), function( c ){
            // Published bit
            c.isEq( "isPublished", isPublished )
            	.when( isPublished, function( c ){
            		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()
    /**
    * @target the closure to execute, the criteria is passed as an argument
    */
    peek( target )
    newCriteria()
        .when( isBoolean( arguments.isPublished ), function( c ){
            // Published bit
            c.isEq( "isPublished", isPublished )
            	.when( isPublished, function( c ){
            		c.isLt( "publishedDate", now() )
    	            .$or( c.restrictions.isNull( "expireDate" ), c.restrictions.isGT( "expireDate", now() ) )
    	            .isEq( "passwordProtection","" );
            	} )
        } )
      .peek( function( c ){
          systemOutput( "HQL after publish: #criteria.getSql()# ");
      } )
      .when( !isNull( arguments.showInSearch ), function( criteria ){	
      	c.isEq( "showInSearch", showInSearch );
       } )
       .peek( function( c ){
          systemOutput( "HQL before listing: #criteria.getSql()# ");
      } )
      .list()
    getInstance( "User" )
        .populate( rc )
        .validateOrFail()
        .save();
    component extends="cborm.models.BaseORMService"{
    
      public UserService function init(){
          super.init( useQueryCaching=true, eventHandling=false );
          return this;    
      }
    
    }
    User.cfc
    component persistent="true" table="users" extends="cborm.models.ActiveEntity"{
    
        function init(){
    
            setCreatedDate( now() );
    
            super.init( useQueryCaching=true, defaultAsQuery=false );
    
            return this;
        }
    
    }
    Arguments

    Key

    Type

    Required

    Default

    Description

    target

    any

    Yes

    ---

    The entity to populate

    xml

    any

    Yes

    ---

    Examples

    Arguments

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    Yes

    ---

    criteria

    struct

    Yes

    ---

    Examples

    Usage

    In order to get started with the base ORM service you need to know how to get access to it. You can do this via WireBox injection DSL or by the model's ID.

    WireBox DSL

    The module also registers a new WireBox DSL called entityservice which can produce virtual or base orm entity services that you can use to inject into your own event handlers or models.

    • entityservice - Inject a global ORM service

    • entityservice:{entityName} - Inject a Virtual entity service according to entityName

    Injection

    You can also request a Base ORM Service via the registered WireBox ID which is exactly the same as the entityService DSL:

    Implementation

    Once you have access to the injected base ORM service, you can use it in all of its glory.

    Important: Please check out the latest API Docs for the latest methods and functionality.

    Once you have a reference to the base ORM service then you can use any of its methods to interact with ORM entities. The drawback about leveraging the base ORM model is that you cannot add custom functions to it or tell it to work on a specific entity for all operations. It is a very simple API, but if you need more control then we can start using other approaches shown below.

    Virtual Services

    We also have a virtual service layer that can be mapped to specific entities and create entity driven service layers virtually. Meaning you don't have to be passing any entity names to the API methods to save you precious typing time.

    Concrete Services

    This is where you can create your own CFC that inherits from our Virtual or Base ORM Service model and either add or override methods. You can read more about it in our Concrete Services Section

    Spring's Hibernate Template
    class BaseORMService
    asStream
    boolean argument to get either an array of objects or a Java stream via cbStreams.

    No casting is necessary on the Id value type as we do this automatically for you.

    Returns

    • This function returns array of entities found

    Arguments

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    true

    ---

    id

    any

    false

    ---

    Examples

    Returns
    • This function returns numeric

    Arguments

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    Yes

    ---

    The name of the entity to delte

    id

    any

    Yes

    ---

    Examples

    Tip: You don't have to use the ORM for everything. Please be pragmatic. If you can't figure it out in 10 minutes or less, move to direct SQL.

    As you will soon discover, they are fantastic but doing it the Java way is not that fun, so we took our lovely ColdFusion dynamic language funkyness and added some ColdBox magic to it.

    The best place to see all of the functionality of the Criteria Builder is to check out the latest API Docs.

    Resources

    You can see below some of the Hibernate documentation on criteria queries.

    1. http://docs.jboss.org/hibernate/core/3.5/reference/en-US/html/querycriteria.html

    2. http://docs.jboss.org/hibernate/core/3.5/javadoc/org/hibernate/Criteria.html

    3. http://docs.jboss.org/hibernate/core/3.5/javadoc/org/hibernate/criterion/Restrictions.html

    4. https://www.baeldung.com/hibernate-criteria-queries

    Hibernate Criteria Queries
    hibernate criteria queries
    HQL
    Arguments

    Key

    Type

    Required

    Default

    description

    query

    string

    Yes

    ---

    params

    any

    No

    ---

    Examples

    AuthorService.cfc
    /**
    * Service to handle author operations.
    */
    component extends="cborm.models.VirtualEntityService" accessors="true" singleton{
    
        // User hashing type
        property name="hashType";
    
        AuthorService function init(){
            // init it via virtual service layer
            super.init( entityName="bbAuthor", useQueryCaching=true );
            setHashType( "SHA-256" );
    
            return this;
        }
    
        function search(criteria){
            var params = {criteria="%#arguments.criteria#%"};
            var r = executeQuery(query="from bbAuthor where firstName like :criteria OR lastName like :criteria OR email like :criteria",params=params,asQuery=false);
            return r;
        }
    
        function saveAuthor(author,passwordChange=false){
            // hash password if new author
            if( !arguments.author.isLoaded() OR arguments.passwordChange ){
                arguments.author.setPassword( hash(arguments.author.getPassword(), getHashType()) );
            }
            // save the author
            save( author );
        }
    
        boolean function usernameFound(required username){
            var args = {"username" = arguments.username};
            return ( countWhere(argumentCollection=args) GT 0 );
        }
    
    }

    populateFromJSON

    Populate an entity with a JSON structure packet. Make sure the names of the properties match the keys in the structure.

    Returns

    • This function returns the populated object

    Arguments

    Examples

    findOrFail

    Finds and returns the first result for the given query or throws an exception if not found, this method delegates to the findIt() method

    Returns

    • This function returns any

    • This function could throw an EntityNotFound exception

    Arguments

    Examples

    createService

    Create a virtual service for a specific entity. Basically a new service layer that inherits from the BaseORMService object but no need to pass in entity names, they are bound to the entity name passed here.

    Returns

    • This function returns VirtualEntityService

    Arguments

    Examples

    list

    List all of the instances of the passed in entity class name with or without any filtering of properties, no HQL needed.

    You can pass in several optional arguments like a struct of filtering criteria, a sortOrder string, offset, max, ignorecase, and timeout. Caching for the list is based on the useQueryCaching class property and the cachename property is based on the queryCacheRegion class property.

    Returns

    • This function returns array if asQuery = false

    • This function returns a query if asQuery = true

    • This function returns a stream if asStream = true

    Arguments

    Examples

    Concrete Virtual Services

    Let's say you are using the virtual service but you find that they do not complete your requirements, or you need some custom methods or change functionality. Then you will be building concrete services that inherit from the virtual entity service. This is the very purpose of these support classes as most of the time you will have custom requirements and your own style of coding. You will do this in two steps:

    1. Inherit from cborm.models.VirtualEntityService

    2. Call the super.init() constructor with the entity to root the service and any other options

    Below is a sample service layer:

    populateFromQuery

    Populate an entity with a query object. Make sure the names of the columns match the keys in the object.

    Returns

    • This function returns the populated object

    executeQuery

    Allows the execution of Custom HQL queries with binding, pagination, and many options. Underlying mechanism is ORMExecuteQuery. The params filtering can be using named or positional.

    Returns

    • This function returns any

    Overview

    The virtual entity service is another support class that can help you create virtual service layers that are bounded to a specific ORM entity for convenience. This class inherits from our Base ORM Service and allows you to do everything the base class provides, except you do not need to specify to which entityName or entity you are working with.

    You can also use this class as a base class and template out its methods to more concrete usages. The idea behind this virtual entity service layer is to allow you to have a very nice abstraction to all the CF ORM capabilities (hibernate) and promote best practices.

    Service Properties

    There are a few properties you can instantiate the virtual service with or set them afterwards that affect operation. Below you can see a nice chart for them:

    var user = ormService.populateFromXML( ormService.new("User"), xml, "User");
    posts = ormService.findAllWhere(entityName="Post", criteria={author="Luis Majano"});
    users = ormService.findAllWhere(entityName="User", criteria={isActive=true});
    artists = ormService.findAllWhere(entityName="Artist", criteria={isActive=true, artist="Monet"});
    // Inject
    inject name="ORMService" inject="BaseORMService@cborm";
    
    // Retrieve
    wireBox.getInstance( "BaseORMService@cborm" );
    component{
    
      inject name="ORMService" inject="entityService";
    
      function saveUser( event, rc, prc ){
          // retrieve and populate a new user object
          var user = populateModel( ORMService.new( "User" ) );
    
          // save the entity using hibernate transactions
          ORMService.save( user );
    
          setNextEvent( "user.list" );
      }
    
      function list( event, rc, prc ){
    
        //get a listing of all users with paging
        prc.users = ORMService.list(
            entityName= "User",
            sortOrder = "fname",
            offset     = event.getValue("startrow",1),
            max         = 20
        );
    
        event.setView( "user/list" );
      }
    }
    
    function index( event, rc, prc ){
        prc.data = ORMService.findAll( "Permission" );
    }
    // Get all user entities
    users = ORMService.getAll(entityName="User", sortOrder="email desc");
    // Get all the following users by id's
    users = ORMService.getAll("User","1,2,3");
    // Get all the following users by id's as array
    users = ORMService.getAll("User",[1,2,3,4,5]);
    // just delete
    count = ormService.deleteByID("User",1);
    
    // delete and flush
    count = ormService.deleteByID("User",4,true);
    
    // Delete several records, or at least try
    count = ormService.deleteByID("User",[1,2,3,4]);
    
    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();
    // delete all blog posts
    ormService.deleteByQuery("from Post");
    // delete query with positional parameters
    ormService.deleteByQuery("from Post as b where b.author=? and b.isActive = :active",['Luis Majano',false]);
    
    // Use query options
    var query = "from User as u where u.isActive=false order by u.creationDate desc"; 
    // first 20 stale inactive users 
    ormService.deleteByQuery(query=query,max=20); 
    // 20 posts starting from my 15th entry
    ormService.deleteByQuery(query=query,max=20,offset=15,flush=true);
    
    // examples with named parameters
    ormService.deleteByQuery("from Post as p where p.author=:author", {author='Luis Majano'})
    component{
        // Concrete ORM service layer
        property name="authorService" inject="security.AuthorService";
        // Aliased 
        property name="authorService" inject="id:AuthorService";
    
        function index( event, rc, prc ){
            // Get all authors or search
            if( len(event.getValue( "searchAuthor", "" )) ){
                prc.authors = authorService.search( rc.searchAuthor );
                prc.authorCount = arrayLen( prc.authors );
             } else {
                prc.authors        = authorService.list( sortOrder="lastName desc", asQuery=false );
                prc.authorCount     = authorService.count();
            }
    
            // View
            event.setView("authors/index");
        }
    }

    The xml string or xml document object to populate with

    root

    string

    false

    The xml root node to start the population with, by default it uses the XMLRoot.

    scope

    string

    No

    Use scope injection instead of setter injection, no need of setters, just tell us what scope to inject to

    trustedSetter

    Boolean

    No

    false

    Do not check if the setter exists, just call it, great for usage with onMissingMethod() and virtual properties

    include

    string

    No

    A list of keys to ONLY include in the population

    exclude

    string

    No

    A list of keys to exclude from the population

    nullEmptyInclude

    string

    No

    A list of keys to NULL when empty, specifically for ORM population. You can also specify "*" for all fields

    nullEmptyExclude

    string

    No

    A list of keys to NOT NULL when empty, specifically for ORM population. You can also specify "*" for all fields

    composeRelationships

    boolean

    No

    true

    When true, will automatically attempt to compose relationships from memento

    A structure of criteria to filter on

    sortOrder

    string

    false

    ---

    The sort ordering

    ignoreCase

    boolean

    false

    false

    timeout

    numeric

    false

    0

    asStream

    boolean

    false

    false

    sortOrder

    string

    false

    ---

    The sort orering of the array

    readOnly

    boolean

    false

    false

    properties

    string

    false

    If passed, you can retrieve an array of properties of the entity instead of the entire entity. Make sure you add aliases to the properties: Ex: 'catId as id'

    asStream

    boolean

    false

    false

    Returns the result as a Java Stream using cbStreams

    A single ID or array of IDs

    flush

    boolean

    No

    false

    transactional

    boolean

    No

    From Property

    Use transactions not

    max

    numeric

    No

    0

    offfset

    numeric

    No

    0

    flush

    boolean

    No

    false

    transactional

    boolean

    No

    From Property

    Use transactions or not

    datasource

    string

    false

    The datasource to use or use the default datasource

    scope

    string

    No

    Use scope injection instead of setter injection, no need of setters, just tell us what scope to inject to

    trustedSetter

    Boolean

    No

    false

    Do not check if the setter exists, just call it, great for usage with onMissingMethod() and virtual properties

    include

    string

    No

    A list of keys to ONLY include in the population

    exclude

    string

    No

    A list of keys to exclude from the population

    nullEmptyInclude

    string

    No

    A list of keys to NULL when empty, specifically for ORM population. You can also specify "*" for all fields

    nullEmptyExclude

    string

    No

    A list of keys to NOT NULL when empty, specifically for ORM population. You can also specify "*" for all fields

    composeRelationships

    boolean

    No

    true

    When true, will automatically attempt to compose relationships from memento

    Key

    Type

    Required

    Default

    Description

    target

    any

    Yes

    ---

    The entity to populate

    JSONString

    string

    Yes

    ---

    The JSON packet to use for population

    timeout

    numeric

    No

    0

    ignoreCase

    boolean

    No

    false

    datasource

    string

    No

    Key

    Type

    Required

    Default

    Description

    query

    string

    No

    ---

    The HQL Query to execute

    params

    any

    No

    {}

    Positional or named params

    No

    Same as BaseService

    eventHandling

    boolean

    No

    true

    useTransactions

    boolean

    No

    true

    defaultAsQuery

    boolean

    No

    true

    datasource

    string

    No

    The default app datasource

    Key

    Type

    Required

    Default

    entityName

    string

    Yes

    ---

    useQueryCaching

    boolean

    No

    Same as BaseService

    queryCacheRegion

    string

    string

    false

    #entityName#.defaultVSCache

    The name of the secondary cache region to use when doing queries via this service

    useQueryCaching

    boolean

    false

    false

    To enable the caching of queries used by this service

    eventHandling

    boolean

    false

    true

    Announce interception events on new() operations and save() operations: ORMPostNew, ORMPreSave, ORMPostSave

    useTransactions

    boolean

    false

    true

    Enables ColdFusion safe transactions around all operations that either save, delete or update ORM entities

    defaultAsQuery

    boolean

    false

    true

    The bit that determines the default return value for list(), executeQuery() as query or array of objects

    datasource

    string

    false

    System Default

    The default datasource to use for all transactions. If not set, we default it to the system datasource or the one declared in the persistent CFC.

    So to create a virtual service you can do this:

    Property

    Type

    Required

    Default

    Description

    entityName

    string

    true

    The entity name to bind the virtual service with.

    queryCacheRegion

    var user = ormService.populateFromJSON( ormService.new("User"), jsonString );
    // My First Post
    ormService.findOrFail("from Post as p where p.author='Luis Majano'");
    // With positional parameters
    ormService.findOrFail("from Post as p where p.author=?", ["Luis Majano"]);
    // with a named parameter (since 0.5)
    ormService.findOrFail("from Post as p where p.author=:author and p.isActive=:active", { author="Luis Majano",active=true} );
    userService = ormService.createService("User");
    userService = ormService.createService("User",true);
    userService = ormService.createService("User",true,"MyFunkyUserCache");
    
    // Remember you can use virtual entity services by autowiring them in via our DSL
    component{
      property name="userService" inject="entityService:User";
      property name="postService" inject="entityService:Post";    
    }
    component extends="cborm.models.VirtualEntityService"{
    
      UserService function init(){
          super.init( entityName="User", useQueryCaching=true, eventHandling=false );
          return this;    
      }
    
    }

    sortOrder

    string

    No

    The sorting order of the listing

    offset

    numeric

    No

    0

    Pagination offset

    max

    numeric

    No

    0

    Max records to return

    timeout

    numeric

    No

    0

    Query timeout

    ignoreCase

    boolean

    No

    false

    Case insensitive or case sensitive searches, we default to case sensitive filtering.

    asQuery

    boolean

    No

    true

    Return query or array of objects

    asStream

    boolean

    No

    false

    Returns the result as a Java Stream using

    Key

    Type

    Required

    Default

    Description

    entityName

    string

    Yes

    ---

    The entity to list

    criteria

    struct

    No

    A struct of filtering criteria for the listing

    Arguments

    Key

    Type

    Required

    Default

    Description

    target

    any

    Yes

    ---

    The entity to populate

    qry

    query

    Yes

    ---

    Examples

    Arguments

    Key

    Type

    Required

    Default

    Description

    query

    string

    Yes

    ---

    The valid HQL to process

    params

    array or struct

    No

    Examples

    Tip: Please remember that you can use ANY method found in the Base ORM Service except that you will not pass an argument of entityName anymore as you have now been bounded to that specific entity.

    Injection Virtual Services

    The WireBox injection DSL has an injection namespace called entityService that can be used to wire in a Virtual Entity Service bound to ANY entity in your application. You will use this DSL in conjunction with the name of the entity to manage it.

    Inject Content

    Description

    entityService:{entity}

    A virtual service based on the {entity}

    Requesting Virtual Services

    You can also request a virtual service via the getInstance() method in your handlers or view a wirebox.getInstance() call:

    Mapping Virtual Services

    You can also leverage the WireBox Binder to map your virtual services so you can abstract a little bit more the construction or even add constructor arguments to their definition and have full control:

    Now you can just use it via the UserService alias:

    https://howtodoinjava.com/hibernate/hibernate-criteria-queries-tutorial/

    findAll

    Find all the entities for the specified query, named or positional arguments or by an example entity

    Returns

    • This function returns array

    Arguments

    Examples

    Ortus Solutions Artifacts Serverapidocs.ortussolutions.com

    populate

    Populate an entity with a structure of name-value pairs. Make sure the names of the properties match the keys in the structure.

    Returns

    • This function returns the populated object

    Active Entity Overview

    This class allows you to implement the pattern in your ORM entities by inheriting from our Active Entity class. This will make your ORM entities get all the functionality of our Virtual and Base ORM services so you can do finds, searches, listings, counts, execute queries, transaction safe deletes, saves, updates, criteria building, and even right from within your ORM Entity.

    The idea behind the Active Entity is to allow you to have a very nice abstraction to all the ColdFusion ORM capabilities (hibernate) and all of our ORM extensions like our ColdBox Criteria Builder. With Active Entity you will be able to:

    • Find entities using a variety of filters and conditions

    Validation

    Validation Functions

    Our active entity object will also give you access to our validation engine () by giving your ORM entities the following functions:

    Declaring Constraints

    users = ormService.list(entityName="User",max=20,offset=10,asQuery=false);
    users = ormService.list(entityName="Art",timeout=10);
    users = ormService.list("User",{isActive=false},"lastName, firstName");
    users = ormService.list("Comment",{postID=rc.postID},"createdDate desc");
    var user = ormService.populateFromQuery( ormService.new( "User" ), ormService.list( "User", { id=4 } ) );
    // simple query
    ormService.executeQuery( "select distinct a.accountID from Account a" );
    // using with list of parameters
    ormService.executeQuery( "select distinct e.employeeID from Employee e where e.department = ? and e.created > ?", ['IS','01/01/2010'] );
    // same query but with paging
    ormService.executeQuery( "select distinct e.employeeID from Employee e where e.department = ? and e.created > ?", ['IS','01/01/2010'],1,30);
    
    // same query but with named params and paging
    ormService.executeQuery( "select distinct e.employeeID from Employee e where e.department = :dep and e.created > :created", {dep='Accounting',created='01/01/2010'],10,20);
    
    // GET FUNKY!!
    component{
        // Virtual service layer based on the User entity
        property name="userService" inject="entityService:User";
        
    }
    userService = getInstance( dsl="entityService:User" );
    
    usersFound = userService.count();
    user = userService.new( {firstName="Luis",lastName="Majano",Awesome=true} );
    userService.save( user );
    
    user = userService.get( "123" );
    
    var users = userService.newCriteria()
        .like("lastName", "%maj%")
        .isTrue("isActive")
        .list(sortOrder="lastName");
    config/WireBox.cfc
    function configure(){
        
        map( "UserService" )
            .to( "cborm.models.VirtualEntityService" )
            .initArg( name="entityName", value="User" )
            .initArg( name="useQueryCaching", value="true" )
            .initArg( name="defaultAsQuery", value="false" );
    }
    handlers/user.cfc
    component{
        
        // Inject Alias
        property name="userService" inject="UserService";
        
        function index( event, rc, prc ){
            var usersFound = userService.count();
            var user = userService.new( {firstName="Luis",lastName="Majano",Awesome=true} );
            userService.save( user );
            
            var user = userService.get( "123" );
            
            var users = userService.newCriteria()
                .like("lastName", "%maj%")
                .isTrue("isActive")
                .list(sortOrder="lastName");
        }
    
    }
     component extends="cborm.models.VirtualEntityService" singleton{
    
        // DI
        property name="settingService"  inject="id:settingService@cb";
        property name="CBHelper"        inject="id:CBHelper@cb";
        property name="log"             inject="logbox:logger:{this}";
    
        /**
        * Constructor
        */
        CommentService function init(){
            super.init( entityName="cbComment", useQueryCaching="true");
            return this;
        }
    
        /**
        * Get the total number of approved comments in the system
        */
        numeric function getApprovedCommentCount(){
            return countWhere( { "isApproved" = true } );
        }
    
        /**
        * Get the total number of unapproved comments in the system
        */
        numeric function getUnApprovedCommentCount(){
            return countWhere( { "isApproved" = false } );
        }
    
        /**
        * Comment listing for UI of approved comments, returns struct of results=[comments,count]
        * @contentID.hint The content ID to filter on
        * @contentType.hint The content type discriminator to filter on
        * @max.hint The maximum number of records to return, 0 means all
        * @offset.hint The offset in the paging, 0 means 0
        * @sortOrder.hint Sort the comments asc or desc, by default it is desc
        */
        function findApprovedComments(
            contentID,
            contentType,
            max=0,
            offset=0,
            sortOrder="desc"
        ){
            var results = {};
            var c = newCriteria();
    
            // only approved comments
            c.isTrue("isApproved");
    
            // By Content?
            if( structKeyExists( arguments,"contentID" ) AND len( arguments.contentID ) ){
                c.eq( "relatedContent.contentID", idCast( arguments.contentID ) );
            }
            // By Content Type Discriminator: class is a special hibernate deal
            if( structKeyExists( arguments,"contentType" ) AND len( arguments.contentType ) ){
                c.createCriteria("relatedContent")
                    .isEq( "class", arguments.contentType );
            }
    
            // run criteria query and projections count
            results.count    = c.count();
            results.comments = c.list(
                offset    = arguments.offset,
                max       = arguments.max,
                sortOrder = "createdDate #arguments.sortOrder#",
                asQuery   = false
            );
    
            return results;
        }
    }

    The query to populate with

    rowNumber

    numeric

    false

    1

    The row to use to populate with.

    scope

    string

    No

    ---

    Use scope injection instead of setter injection, no need of setters, just tell us what scope to inject to

    trustedSetter

    Boolean

    No

    false

    Do not check if the setter exists, just call it, great for usage with onMissingMethod() and virtual properties

    include

    string

    No

    ---

    A list of columns to ONLY include in the population

    exclude

    string

    No

    ---

    A list of columns to exclude from the population

    nullEmptyInclude

    string

    No

    A list of keys to NULL when empty, specifically for ORM population. You can also specify "*" for all fields

    nullEmptyExclude

    string

    No

    A list of keys to NOT NULL when empty, specifically for ORM population. You can also specify "*" for all fields

    composeRelationships

    boolean

    No

    true

    When true, will automatically attempt to compose relationships from memento

    Positional or named parameters

    offset

    numeric

    No

    0

    Pagination offset

    max

    numeric

    No

    0

    Max records to return

    timeout

    numeric

    No

    0

    Query timeout

    asQuery

    boolean

    No

    true

    Return query or array of objects

    unique

    boolean

    No

    false

    Return a unique result

    datasource

    string

    No

    ---

    Use a specific or default datasource

    asStream

    boolean

    No

    false

    Returns the result as a Java Stream using cbStreams

    cbStreams
    Logo

    offset

    numeric

    No

    0

    max

    numeric

    No

    0

    timeout

    numeric

    No

    0

    ignoreCase

    boolean

    No

    false

    datasource

    string

    No

    asStream

    boolean

    No

    false

    Key

    Type

    Required

    Default

    Description

    query

    string

    No

    ---

    The HQL Query to execute

    params

    any

    No

    [runtime expression]

    Named or positional params

    // find all blog posts
    ormService.findAll("Post");
    // with a positional parameters
    ormService.findAll("from Post as p where p.author=?",['Luis Majano']);
    // 10 posts from Luis Majano staring from 5th post ordered by release date
    ormService.findAll("from Post as p where p.author=? order by p.releaseDate",['Luis majano'],offset=5,max=10);
    
    // Using paging params
    var query = "from Post as p where p.author='Luis Majano' order by p.releaseDate" 
    // first 20 posts 
    ormService.findAll(query=query,max=20) 
    // 20 posts starting from my 15th entry
    ormService.findAll(query=query,max=20,offset=15);
    
    // examples with named parameters
    ormService.findAll("from Post as p where p.author=:author", {author='Luis Majano'})
    ormService.findAll("from Post as p where p.author=:author", {author='Luis Majano'}, max=20, offset=5);
    
    // query by example
    user = ormService.new(entityName="User",firstName="Luis");
    ormService.findAll( example=user );
    Arguments

    Key

    Type

    Required

    Default

    Description

    target

    any

    Yes

    ---

    The entity to populate

    memento

    struct

    Yes

    ---

    INFO With composeRelationships=true, you can populate one-to-many, many-to-one, many-to-many, and one-to-one relationships from property values in the memento. For 'many-to-one' and 'one-to-one' relationships, the value of the property in the memento should be a single value of the primary key of the target entity to be loaded. For 'one-to-many' and 'many-to-many' relationships, the value of the property in the memento should a comma-delimited list or array of the primary keys of the target entities to be loaded.

    Examples

    ORM paging
  • Specify order, searches, criterias and grouping of orm listing and searches

  • Use DLM style hibernate operations for multiple entity deletion, saving, and updating

  • Check for existence of records

  • Check for counts using criterias

  • Use our extensive ColdBox Criteria Builder to build Object Oriented HQL queries

  • Validate your entity using cbValidation

  • Configuration

    To work with Active Entity you must do a few things to tell ColdBox and Hibernate you want to use Active Entity:

    1. Enable the ORM in your Application.cfc with event handling turned on, manage session and flush at request end as false. This will allow Hibernate to talk to the cborm event handling objects.

    2. Enable the orm configuration structure in your ColdBox configuration to allow for ColdBox to do entity injections via WireBox.

    Application.cfc

    The following are vanilla configurations for enabling the ORM in ColdFusion:

    Module Settings

    Open your config/ColdBox.cfc and either un-comment or add the following settings:

    This enables WireBox dependency injection, which we need for ActiveEntity to work with validation and other features. Check out our installation section if you need a refresher.

    Building Entities

    Once your configuration is done we can now focus on building out your Active Entities. You will do so by creating your entities like normal ORM objects but with two additions:

    1. They will inherit from our base class: cborm.models.ActiveEntity

    2. If you have a constructor then it must delegate to the super class via super.init()

    Please remember that your entities inherit all the functionality of the base and virtual services. Except no entity names or datasources are passed around.

    Active Record
    validation

    This makes it really easy for you to validate your ORM entities in two easy steps:

    1) Add your validation constraints to the entity

    2) Call the validation methods

    Let's see the entity code so you can see the constraints:

    Validating Constraints

    Now let's check out the handlers to see how to validate the entity via the isValid() function:

    Please remember that the isValid() function has several arguments you can use to fine tune the validation:

    • fields

    • constraints

    • locale

    • excludeFields

    Displaying Errors

    You can refer back to the cbValidation docs for displaying errors:

    Here are the most common methods for retreving the errors from the Result object via the getValidationResults() method:

    • getResultMetadata()

    • getFieldErrors( [field] )

    • getAllErrors( [field] )

    • getAllErrorsAsJSON( [field] )

    • getAllErrorsAsStruct( [field] )

    • getErrorCount( [field] )

    • hasErrors( [field] )

    • getErrors()

    The API Docs in the module (once installed) will give you the latest information about these methods and arguments.

    Unique Property Validation

    We have also integrated a UniqueValidator from the validation module into our ORM module. It is mapped into WireBox as UniqueValidator@cborm so you can use it in your model constraints like so:

    cbValidation

    Basic Crud - Services

    Let's do a basic example of how to work with cborm when doing basic CRUD (Create-Read-Update-Delete). We will generate a ColdBox App, connect it to a database and leverage a virtual service layer for a nice quick CRUD App.

    The source code for this full example can be found in Github: https://github.com/coldbox-samples/cborm-crud-demo or in ForgeBox: https://forgebox.io/view/cborm-crud-demo

    ColdBox App

    Let's start by creating a ColdBox app and preparing it for usage with ORM:

    Setup Environment

    Season the environment file (.env) with your database credentials and make sure that database exists:

    Setup ORM

    Now open the Application.cfc and let's configure the ORM by adding the following in the pseudo constructor and adding two lines of code to the request start so when we reinit the APP we can also reinit the ORM.

    To change the datasource name to something you like then update it here and in the .cfconfig.json file. Once done, issue a server restart and enjoy your new datasource name.

    Start Server

    Let's start a server and start enjoying the fruits of our labor:

    If you get a Could not instantiate connection provider: org.lucee.extension.orm.hibernate.jdbc.ConnectionProviderImpl error on startup here. It means that you hit the stupid Lucee bug where on first server start the ORM is not fully deployed. Just issue a server restart to resolve this.

    Create Entity - Person.cfc

    Let's start by creating a Person object with a few properties, let's use CommandBox for this and our super duper coldbox create orm-entity command:

    This will generate the models/Person.cfc as an ActiveEntity object and even create the unit test for it.

    Setup for BDD

    Since we love to promote tests at Ortus, let's configure our test harness for ORM testing. Open the /tests/Application.cfc and add the following code to setup the ORM and some functions for helping us test.

    Now that we have prepared the test harness for ORM testing, let's test out our Person with a simple unit test. We don't over test here because our integration test will be more pragmatic and cover our use cases:

    Basic CRUD

    We will now generate a handler and do CRUD actions for this Person:

    This creates the handlers/persons.cfc with the CRUD actions and a nice index action we will use to present all persons just for fun!

    Please note that this also generates the integrations tests as well under /tests/specs/integration/personsTest.cfc

    Inject Service

    Open the handlers/persons.cfc and in the pseudo-constructor let's inject a virtual ORM service layer based on the Person entity:

    The cborm module gives you the entityService:{entityName} DSL which allows you to inject virtual service layers according to entityName. With our code above we will have a personService in our variables scope injected for us.

    Create

    We will get an instance of a Person, populate it with data and save it. We will then return it as a json memento. The new() method will allow you to pass a struct of properties and/or relationships to populate the new Person instance with. Then just call the save() operation on the returned object.

    You might be asking yourself: Where does this magic getMemento() method come from? Well, it comes from the module which inspects ORM entities and injects them with this function to allow you to produce raw state from entities. (Please see: )

    Read

    We will get an instance according to ID and show it's memento in json. There are many ways in the ORM service and Active Entity to get objects by criteria,

    In this example, we use the get() method which retrieves a single entity by identifier. Also note the default value of 0 used as well. This means that if the incoming id is null then pass a 0. The orm services will detect the 0 and by default give you a new Person object, the call will not fail. If you want your call to fail so you can show a nice exception for invalid identifiers you can use getOrFail() instead.

    Update

    Now let's retrieve an entity by Id, update it and save it again!

    Delete

    Now let's delete an incoming entity identifier

    Note that you have two choices when deleting by identifier:

    1. Get the entity by the ID and then send it to be deleted

    2. Use the deleteById() and pass in the identifier

    The latter allows you to bypass any entity loading, and do a pure HQL delete of the entity via it's identifier. The first option is more resource intensive as it has to do a 1+ SQL calls to load the entity and then a final SQL call to delete it.

    List All

    For extra credit, we will get all instances of Person and render their memento's

    That's it! We are now rolling with basic CRUD cborm style!

    BDD Tests

    Here are the full completed BDD tests as well

    What's New With 2.0.0

    Compatibility Updates

    • You will need to move the orm configuration structure in your config/ColdBox.cfc to the moduleSettings struct and rename it to cborm

    var user = ormService.populate( ormService.new("User"), data );
    
    // populate with includes only
    var user = ormService.populate( ormService.new("User"), data, "fname,lname,email" );
    
    //populate with excludes
    var user = ormService.populate(target=ormService.new("User"),memento=data,exclude="id,setup,total" );
    
    // populate with null values when value is empty string
    var user = ormService.populate(target=ormService.new("User"),memento=data,nullEmptyInclude="lastName,dateOfBirth" );
    
    // populate many-to-one relationship
    var data = {
        firstName = "Luis",
        role = 1 // "role" is the name of the many-to-one relational property, and one is the key value
    };
    var user = ormService.populate( target=ormService.new("User"), memento=data, composeRelationships=true );
    // the role relationship will be composed, and the value will be set to the appropriate instance of the Role model
    
    // populate one-to-many relationship
    var data = {
        firstName = "Luis",
        favColors = "1,2,3" ( or [1,2,3] ) // favColors is the name of the one-to-many relational property, and 1, 2 and 3 are key values of favColor models
    };
    var user = ormService.populate( target=ormService.new("User"), memento=data, composeRelationships=true );
    // the favColors property will be set to an array of favColor entities
    
    // only compose some relationships
    var data = {
        firstName = "Luis",
        role = 1,
        favColors = [ 1, 3, 19 ]
    };
    var user = ormService.populate( target=ormService.new("User"), memento=data, composeRelationships=true, exclude="favColors" );
    // in this example, "role" will be composed, but "favColors" will be excluded
    Application.cfc
    component{
    
        // Enable ORM
        this.ormEnabled       = true;
        // ORM Datasource
        this.datasource          = "contacts";
        // ORM configuration settings
        this.ormSettings      = {
            // Location of your entities, default is your convention model folder
            cfclocation = [ "models" ],
            // Choose if you want ORM to create the database for you or not?
            dbcreate = "update",
            // Log SQL or not
            logSQL = true,
            // Don't flush at end of requests, let Active Entity manage it for you
            flushAtRequestEnd = false,
            // Don't manage session, let Active Entity manage it for you
            autoManageSession = false,
            // Active ORM events
            eventHandling       =  true,
            // Use the ColdBox WireBox Handler for events
            eventHandler = "cborm.models.EventHandler"
        };
    }
    config/Coldbox.cfc
    moduleSettings = {
        cborm = {
            injection = {
                // enable entity injection via WireBox
                enabled = true, 
                // Which entities to include in DI ONLY, if empty include all entities
                include = "", 
                // Which entities to exclude from DI, if empty, none are excluded
                exclude = ""
            }
        }
    }
    models/User.cfc
    component persistent="true" table="users" extends="cborm.models.ActiveEntity"{
        
        property name="id" column="user_id" fieldType="id" generator="uuid";
    	property name="firstName";
    	property name="lastName";
    	property name="userName";
    	property name="password";
    	property name="lastLogin" ormtype="date";
    	
    	function init(){
    	   return super.init();
    	}
    
    }
    /**
     * Validate the ActiveEntity with the coded constraints -> this.constraints, or passed in shared or implicit constraints
     * The entity must have been populated with data before the validation
     *
     * @fields One or more fields to validate on, by default it validates all fields in the constraints. This can be a simple list or an array.
     * @constraints An optional shared constraints name or an actual structure of constraints to validate on.
     * @locale An optional locale to use for i18n messages
     * @excludeFields An optional list of fields to exclude from the validation.
     */
    boolean function isValid(
    	string fields="*",
    	any constraints="",
    	string locale="",
    	string excludeFields=""
    ){
    
    /**
    * Get the validation results object.  This will be an empty validation object if isValid() has not being called yet.
    */
    cbvalidation.models.result.IValidationResult function getValidationResults(){
    models/User.cfc
    component persistent="true" extends="cborm.models.ActiveEntity"{
        
        // Properties
        property name="firstName";
        property name="lastName";
        property name="email";
        property name="username";
        property name="password";
    
        // Validation Constraints
        this.constraints = {
            "firstName" = {required=true}, 
            "lastName"  = {required=true},
            "email"     = {required=true,type="email"},
            "username"  = {required=true, size="5..10"},
            "password"  = {required=true, size="5..10"}
        };
    }
    handlers/users.cfc
    component{
    
        property name="messagebox" inject="messagebox@cbmessagebox";
    
        function index(event,rc,prc){
            prc.users = getInstance( "User" ).list( sortOrder="lastName asc" );
            event.setView( "users/list" );
        }
    
        function save(event,rc,prc){
            event.paramValue( "id", -1 );
            
            var oUser = getInstance( "User" )
                .getOrFail( rc.id )
                .populate( rc )
    
            if( oUser.isValid() {
                oUser.save();
                flash.put( "notice", "User Saved!" );
                relocate( "users.index" );
            }
            else{
                messagebox.error( messageArray=oUser.getValidationResults().getAllErrors() );
                editor( event, rc, prc );
            }
    
        }
    
        function editor(event,rc,prc){
            event.paramValue( "id", -1 );
            prc.user = getInstance( "User" ).getOrFail( rc.id );
            event.setView( "users/editor" );
        }
    
        function delete(event,rc,prc){
            event.paramValue( "id", -1 );
            getInstance( "User" )
                .deleteById( rc.id );
            flash.put( "notice", "User Removed!" );
            relocate( "users.index" );
        }
    }
    { username : { validator : "UniqueValidator@cborm", required : true } }
    # Create folder
    mkdir myapp --cd
    # Scaffold App
    coldbox create app
    # Install cborm, dotenv, and cfconfig so we can get the CFML engine talking to the DB fast.
    install cborm,commandbox-dotenv,commandbox-cfconfig
    # Update the .env file
    cp .env.example .env

    The structure of name-value pairs to try to populate the entity with

    scope

    string

    No

    Use scope injection instead of setter injection, no need of setters, just tell us what scope to inject to

    trustedSetter

    Boolean

    No

    false

    Do not check if the setter exists, just call it, great for usage with onMissingMethod() and virtual properties

    include

    string

    No

    A list of keys to ONLY include in the population

    exclude

    string

    No

    A list of keys to exclude from the population

    nullEmptyInclude

    string

    No

    A list of keys to NULL when empty, specifically for ORM population. You can also specify "*" for all fields

    nullEmptyExclude

    string

    No

    A list of keys to NOT NULL when empty, specifically for ORM population. You can also specify "*" for all fields

    composeRelationships

    boolean

    No

    true

    When true, will automatically attempt to compose relationships from memento

    mementifier
    https://forgebox.io/view/mementifier
    to standardize it to module settings.
    • deleteByQuery() reworked entirely to do native bulk delete queries. It now also returns the number of records removed

    • The evict() method was renamed to evictCollection() to better satisfy the same contract in hibernate

    • The evictEntity() method was renamed to evict() to better satisfay the same contract in hibernate

    • Removed byExample on many listing methods

    General Updates

    • Mementifier is now a dependency for cborm (www.forgebox.io/view/mementifier), which can be used for producing state out of ORM entities for auditing or building JSON Api's.

    • cbStreams is now a dependency for cborm (www.forgebox.io/view/cbstreams), all criteria queries and major listing methods support the return of streams instead of array of objects

    • Full Null Support

    • Performance update on creating active entities as datasource discovery has been reworked

    • Updated build process to latest in Ortus template

    • Dropped Railo, Lucee 4.5, ACF11 support

    • More direct scoping for performance updates

    • Optimized EventHandler so it is lighter and quicker when doing orm injections

    • Documented all functions with extra examples and notes and hibernate references

    • ColdBox 5 and 4 discrete ORM Injection DSLs

    Criteria Queries

    • They have been adapted to work with Hibernate 3, 4 and 5

    • New fail fast method for get() -> getOrFail() to throw an entity not found exception

    • New alias methods for controlling the result transformations asStruct(), 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.

    • When calling native restrictions, no more reflection is used to discover the restriction type thus increasing over 70% in performance when creating criteria queries

    • You can now negate any criteria restriction by prefixing it with a not. So you can do: .notEq(), notBetween(), notIsNull(), notIsIn() and much more.

    • The list() method has a new asStream boolean argument that if true, will return the results as a cbStream. ((www.forgebox.io/view/cbStreams))

    • New Methods: idCast() and autoCast() added for quick casting of values

    • New method: queryHint() so you can add your own vendor specific query hints for optimizers.

    • New method: comment( string ) so you can add arbitrary comments to the generated SQL, great for debugging

    • sqlRestriction() deprecated in favor of the shorthand notation: sql()

    • The 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:

    The available types are the following which match the Hibernate Types

    • Detached Criteria builder now has a maxResults( maxResults ) method to limit the results by

    • Detached Criteria sql projections now take aliases into account

    • SQL Projections and SQL Group By projections now respect aliases

    Base ORM Service

    • New Fail fast methods: getOrFail() proxies to get(), findOrFail() proxies to findIt() that if not entity is produced will throw a EntityNotFound exception

    • All listing methods can now return the results as a cbStream by passing the asStream boolean argument.

    • Removed criteriaCount(), criteriaQuery() from BaseService, this was the legacy criteria builder approach, please use newCriteria() instead.

    • Update getEntityGivenName to support ACF2018

    • Lazy loading BeanPopulator for performance on creations

    • Lazy loading ORMEventHandler for performance on creations

    • Lazy loading restrictions for performance on creations

    • Base service can now be initialized with a datasource, or uses the default one declared

    • Added optional datasource to many listing methods

    • Added consistency on querying options to all major functions to include ignoreCase, sorting and timeouts.

    • Added ability to getAll() to retrieve read only entities using the readOnly argument.

    • The 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.

    • New method: idCast( entity, id ) to auto cast your entity id value to java type automatically for you, no more javacasting

    • New method: autoCast( entity, propertyName, value ) to auto cast any value for any entity property automatically, no more javacasting.

    • New method: getKeyValue( entity ) which will give you the value of the entity's unique identifier

    • New method: isDirty( entity ) which will let you know if the entity has dirty values or has its values changed since loaded from the db

    • New method: getEntityMetadata( 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.

    • ORM Encapsulation of hibernate metadata retrieval via getEntityMetadata()

    • deleteByQuery() reworked entirely to do native bulk delete queries. It now also returns the number of records removed

    • deleteWhere() missing flush argument, added datasource as well

    • New properties: wirebox : a WireBox reference already injected, logger : a prepared logger for the class, datasource The default datasource or constructed datasource for the class.

    • Logging of all activity now available via the debug level, even for dynamic methods.

    • Refactored all dynamic finders and counters to their own class, which improves not only performance but weight of orm service based entities.

    • All dynamic method calls can now return cbStreams as the results

    • All dynamic method calls accept a structure as an argument or named as options that can have the following keys now:

    • All dynamic finders/counters values are autocasted, you no longer need to cast the values, we will do this for you. You can turn it off via the autocast:false in the options to the calls.

    Virtual Entity Service

    Remember this entity extends Base Service, so we get all the features above plus the following:

    Active Entity

    Remember this entity extends the Virtual Service, so we get all the features above plus the following:

    • Faster creation speeds due to lazy loading of dependencies and better datasource determination.

    • refresh(), merge(), evict() refactored to encapsulate login in the base orm service and not itself

    # ColdBox Environment
    APPNAME=ColdBox
    ENVIRONMENT=development
    
    # Database Information
    DB_CONNECTIONSTRING=jdbc:mysql://127.0.0.1:3306/cborm?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useLegacyDatetimeCode=true
    DB_CLASS=com.mysql.jdbc.Driver
    DB_DRIVER=MySQL
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=cborm
    DB_USER=root
    DB_PASSWORD=cborm
    
    # S3 Information
    S3_ACCESS_KEY=
    S3_SECRET_KEY=
    S3_REGION=us-east-1
    S3_DOMAIN=amazonaws.com
    Application.cfc
    // Locate the cborm module for events
    this.mappings[ "/cborm" ] = COLDBOX_APP_ROOT_PATH & "modules/cborm";
    
    // The default dsn name in the ColdBox scaffold
    this.datasource = "coldbox"; 
    // ORM Settings + Datasource
    this.ormEnabled = "true";
    this.ormSettings = {
        cfclocation = [ "models" ], // Where our entities exist
        logSQL = true, // Remove after development to false.
        dbcreate = "update", // Generate our DB
        automanageSession = false, // Let cborm manage it
        flushAtRequestEnd = false, // Never do this! Let cborm manage it
        eventhandling = true, // Enable events
        eventHandler = "cborm.models.EventHandler", // Who handles the events
        skipcfcWithError = true // Yes, because we must work in all CFML engines
    };
    
    // request start
    public boolean function onRequestStart( string targetPage ){
        // If we reinit our app, reinit the ORM too
        if( application.cbBootstrap.isFWReinit() )
            ormReload();
    
        // Process ColdBox Request
        application.cbBootstrap.onRequestStart( arguments.targetPage );
    
        return true;
    }
    # Start a default Lucee Server
    server start
    coldbox create orm-entity 
        entityName="Person"
        properties=name,age:integer,lastVisit:timestamp
    Person.cfc
    /**
     * A cool Person entity
     */
    component persistent="true" table="Person"{
    
        // Primary Key
        property name="id" fieldtype="id" column="id" generator="native" setter="false";
    
        // Properties
        property name="name" ormtype="string";
        property name="age" ormtype="numeric";
        property name="lastVisit" ormtype="timestamp";
    
        // Validation
        this.constraints = {
            // Example: age = { required=true, min="18", type="numeric" }
        };
    
    }
    /tests/Application.cfc
        // Locate the cborm module for events
        this.mappings[ "/cborm" ] = rootPath & "modules/cborm";
    
        // ORM Settings + Datasource
        this.datasource = "coldbox"; // The default dsn name in the ColdBox scaffold
        this.ormEnabled = "true";
        this.ormSettings = {
            cfclocation = [ "models" ], // Where our entities exist
            logSQL = true, // Remove after development to false.
            dbcreate = "update", // Generate our DB
            automanageSession = false, // Let cborm manage it
            flushAtRequestEnd = false, // Never do this! Let cborm manage it
            eventhandling = true, // Enable events
            eventHandler = "cborm.models.EventHandler", // Who handles the events
            skipcfcWithError = true // Yes, because we must work in all CFML engines
        };
    
        public boolean function onRequestStart( string targetPage ){
            ormReload();
            return true;
        }
    /tests/specs/unit/PersonTest.cfc
    component extends="coldbox.system.testing.BaseTestCase"{
    
        function run(){
            describe( "Person", function(){
                it( "can be created", function(){
                    expect( getInstance( "Person" ) ).toBeComponent()
                });
            });
        }
    
    }
    coldbox create handler 
        name="persons" 
        actions="index,create,show,update,delete" 
        views=false
    /handlers/persons.cfc
    /**
     * I manage Persons
     */
    component{
    
     // Inject our service layer
     property name="personService" inject="entityService:Person";
    
    }
    /**
     * create a person
     */
    function create( event, rc, prc ){
        prc.person = personService
            .new( {
                name     : "Luis",
                age     : 40,
                lastVisit : now()
            } );
        ;
        return personService
            .save( prc.person )
            .getMemento( includes="id" );
    }
    /**
     * show a person
     */
    function show( event, rc, prc ){
        return personService
            .get( rc.id ?: 0 )
            .getMemento( includes="id" );
    }
    /**
     * show a person
     */
    function show( event, rc, prc ){
        return personService
            .getOrFail( rc.id ?: -1 )
            .getMemento( includes="id" );
    }
    /**
     * Update a person
     */
    function update( event, rc, prc ){
        prc.person = personService
            .getOrFail( rc.id ?: -1 )
            .setName( "Bob" )
        return personService
            .save( prc.person )
            .getMemento( includes="id" );
    }
    /**
     * Delete a Person
     */
    function delete( event, rc, prc ){
        try{
            personService
                .getOrFail( rc.id ?: '' )
                .delete();
            // Or use the shorthnd notation which is faster
            // getIntance( "Person" ).deleteById( rc.id ?: '' )
        } catch( any e ){
            return "Error deleting entity: #e.message# #e.detail#";
        }
    
        return "Entity Deleted!";
    }
    /**
     * List all Persons
     */
    function index( event, rc, prc ){
        return personService
            // List all as array of objects
            .list( asQuery=false )
            // Map the entities to mementos
            .map( function( item ){
                return item.getMemento( includes="id" );
            } );
    }
    /tests/specs/integration/personsTest.cfc
    component extends="coldbox.system.testing.BaseTestCase" appMapping="/"{
    
        function run(){
    
            describe( "persons Suite", function(){
    
                aroundEach( function( spec ) {
                    setup();
                    transaction{
                        try{
                            arguments.spec.body();
                        } catch( any e ){
                            rethrow;
                        } finally{
                            transactionRollback();
                        }
                    }
                   });
    
                it( "index", function(){
                    var event = this.GET( "persons.index" );
                    // expectations go here.
                    expect( event.getRenderedContent() ).toBeJSON();
                });
    
                it( "create", function(){
                    var event = this.POST(
                        "persons.create"
                    );
                    // expectations go here.
                    var person = event.getPrivateValue( "Person" );
                    expect( person ).toBeComponent();
                    expect( person.getId() ).notToBeNull();
                });
    
                it( "show", function(){
                    // Create mock
                    var event = this.POST(
                        "persons.create"
                    );
                    // Retrieve it
                    var event = this.GET(
                        "persons.show", {
                            id : event.getPrivateValue( "Person" ).getId()
                        }
                    );
                    // expectations go here.
                    var person = event.getPrivateValue( "Person" );
                    expect( person ).toBeComponent();
                    expect( person.getId() ).notToBeNull();
                });
    
                it( "update", function(){
                    // Create mock
                    var event = this.POST(
                        "persons.create"
                    );
                    var event = this.POST(
                        "persons.update", {
                            id : event.getPrivateValue( "Person" ).getId()
                        }
                    );
                    // expectations go here.
                    var person = event.getPrivateValue( "Person" );
                    expect( person ).toBeComponent();
                    expect( person.getId() ).notToBeNull();
                    expect( person.getName() ).toBe( "Bob" );
                });
    
                it( "delete", function(){
                    // Create mock
                    var event = this.POST(
                        "persons.create"
                    );
                    // Create mock
                    var event = this.DELETE(
                        "persons.delete", {
                            id : event.getPrivateValue( "Person" ).getId()
                        }
                    );
                    expect( event.getRenderedContent() ).toInclude( "Entity Deleted" );
                });
    
            });
    
        }
    
    }
    config/ColdBox.cfc
    moduleSettings = {
    
    
        cborm = {
            inject = {
                enabled = true,
                includes = "",
                excludes = ""
            }
        }
    
    };
    restrictions.sql( "userName = ? and firstName like ?", [
        { value : "joe", type : "string" },
        { value : "%joe%", type : "string" }
    ] );
    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"
    };
    {
        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" } );

    Getting Started

    A criteria builder object can be requested from our Base ORM services or a virtual service or an ActiveEntity, which will bind itself automatically to the requested entity, by calling on the newCriteria() method. The corresponding class is: cborm.models.CriteriaBuilder

    Criteria Object - newCriteria()

    The arguments for the newCriteria() method are:

    If you call newCriteria() from a virtual service layer or Active Entity, then you don't pass the entityName argument as it roots itself automatically.

    Restrictions

    This criteria object will then be used to add restrictions to build up the exact query you want. Restrictions are basically your where statements in SQL and they build on each other via ANDs by default. For example, only retrieve products with a price over $30 or give me only active users.

    We provide you with tons of available and if none of those match what you need, you can even use a-la-carte SQL restrictions, in which you can just use SQL even with parameters. You can also do OR statements or embedded ANDs, etc.

    Tip: Every restriction can also be negated by using the not prefix before each method: notEq(), notIn(), notIsNull()

    Associations

    You can also use your restrictions on the associated entity data. This is achieved via the methods section.

    Query Modifiers

    You can also add for the execution of the query. This can be sorting, timeouts, join types and so much more.

    • cache() - Enable caching of this query result, provided query caching is enabled for the underlying session factory.

    • cacheRegion() - Set the name of the cache region to use for query result caching.

    • comment() - Add a comment to the generated SQL.

    Result Modifiers

    You can also tell Hibernate to transform the results to other formats for you once you retrieve them.

    • asDistinct() - Applies a result transformer of DISTINCT_ROOT_ENTITY

    • asStruct() - Applies a result transformer of ALIAS_TO_ENTITY_MAP so you get an array of structs instead of array of objects

    • asStream() - Get the results as a CBstream

    Results

    Now that the criteria builder object has all the restrictions and modifiers attached when can execute the SQL. Please note that you can store a criteria builder object if you wanted to. It is lazy evaluated, it just represents your SQL. It will only execute when you need it to execute via the following finalizer methods:

    • list() - Execute the criteria queries you have defined and return the results as an array of objects

    • get() - Convenience method to return a single instance that matches the built up criterias query, or null if the query returns no results.

    • getOrFail() - Convenience method to return a single instance that matches the built up criterias query, or throws an exception if the query returns no results

    Logging

    There are several methods available to you in the criteria objects to give you the actual SQL or HQL to execute, even with bindings. These are a true life-saver.

    • logSQL( label ) - Allows for one-off sql logging at any point in the process of building up CriteriaBuilder; will log the SQL state at the time of the call

    • getSQL() - Returns the SQL string that will be prepared for the criteria object at the time of request

    • getPositionalSQLParameters() - Returns a formatted array of parameter value and types

    https://coldbox-validation.ortusbooks.com/overview/coldbox-validation/displaying-errorscoldbox-validation.ortusbooks.com

    queryCacheRegion

    string

    false

    criteria.{entityName}

    The name of the cache region to use

    datasource

    string

    false

    System Default

    The datasource to bind the criteria query on, defaults to the one in this ORM service

    fetchSize() - Set a fetch size for the underlying JDBC query.

  • firstResult() - Set the first result to be retrieved or the offset integer

  • maxResults() - Set a limit upon the number of objects to be retrieved.

  • order() - Add an ordering to the result set, you can add as many as you like

  • queryHint() - Add a DB query hint to the SQL. These differ from JPA's QueryHint, which is specific to the JPA implementation and ignores DB vendor-specific hints. Instead, these are intended solely for the vendor-specific hints, such as Oracle's optimizers. Multiple query hints are supported; the Dialect will determine concatenation and placement.

  • readOnly() - Set the read-only/modifiable mode for entities and proxies loaded by this Criteria, defaults to readOnly=true

  • timeout() - Set a timeout for the underlying JDBC query.

  • count() - Get the record count using hibernate projections for the given criterias

  • GetSqlLog() - Retrieves the SQL Log

  • startSqlLog() - Triggers CriteriaBuilder to start internally logging the state of SQL at each iterative build

  • stopSqlLog() - Stop the internal logging.

  • logSql() - Allows for one-off sql logging at any point in the process of building up CriteriaBuilder; will log the SQL state at the time of the call

  • canLogSql() - Returns whether or not CriteriaBuilder is currently configured to log SQL

  • Argument

    Type

    Required

    Default

    Description

    entityName

    string

    true

    ---

    The name of the entity to bind this criteria builder with, the initial pivot.

    useQueryCaching

    boolean

    false

    false

    To allow for query caching of list() operations

    restrictions
    association
    modifiers
    // orm service
    ormService.newCriteria( "User" );
    
    // virtual service
    productService.newCriteria();
    
    // active entity
    getInstance( "User" ).newCriteria();
    productService
        .newCriteria()
        .ge( "price", 30 )
        .count();
        
    userService
        .newCriteria()
        .isTrue( "isActive" )
        .notIsNull( "lastLogin" )
        .orderBy( "lastLogin desc" )
        .firstResult()
        .get();
    
    
    // A-la-carte SQL restrictions 
    var userStream = userService
        .newCriteria()
        .sql( "userName = ? and firstName like ? and lastLogin >= ?", [
        	{ value : "joe", type : "string" },
        	{ value : "%joe%", type : "string" }
            { value : incomingDate, type : "timestamp" }
        ] )
        .list( asStream = true );
        
    
    var c = newCriteria();
    var results = c.like("firstName","Lui%") // restriction
         .maxResults( 50 ) // modifier
         .order("balance","desc") // modifier
         // AND restrictions
         .and( 
              c.restrictions.between( "balance", 200, 300),
              c.restrictions.eq("department", "development")
         )
         // Retrieve a list
         .list();
    var results = c.like("firstName","Lui%") // restriction
         .firstResult( 25 ) // modifier
         .maxResults( 50 ) // modifier
         .order( "balance", "desc" ) // modifier
         .timeout( 
         // AND restrictions
         .and( 
              c.restrictions.between( "balance", 200, 300),
              c.restrictions.eq("department", "development")
         )
         // Retrieve a list
         .list();
    productService
        .newCriteria()
        .ge( "price", 30 )
        .count();
        
    userService
        .newCriteria()
        .isTrue( "isActive" )
        .notIsNull( "lastLogin" )
        .orderBy( "lastLogin desc" )
        .firstResult()
        .get();
    var sql = userService
        .newCriteria()
        .sql( "userName = ? and firstName like ? and lastLogin >= ?", [
        	{ value : "joe", type : "string" },
        	{ value : "%joe%", type : "string" }
            { value : incomingDate, type : "timestamp" }
        ] )
        .getSqlLog();
        
    // You can also use peek()
    var results = userService
        .newCriteria()
        .sql( "userName = ? and firstName like ? and lastLogin >= ?", [
        	{ value : "joe", type : "string" },
        	{ value : "%joe%", type : "string" }
            { value : incomingDate, type : "timestamp" }
        ] )
        .peek( function( c ){
            log.debug( "sql log: #c.getSqlLog()#" );
        });
        .list();

    Restrictions

    The ColdBox restrictions class allows you to create criterions upon certain properties, associations and even SQL for your ORM entities. This is the meat and potatoes of criteria queries, where you build a set of criterion to match against or in SQL terms, your WHERE statements.

    The ColdBox criteria class offers almost all of the criterion methods found in the native hibernate Restrictions class:

    • http://docs.jboss.org/hibernate/core/3.5/javadoc/org/hibernate/criterion/Restrictions.html

    • https://docs.jboss.org/hibernate/core/4.3/javadocs/org/hibernate/criterion/Restrictions.html

    If there isn't one defined in the CFML equivalent then don't worry, just call it like is appears in the Javadocs and we will proxy the call to the native Hibernate class for you.

    Direct Reference

    You can get a direct reference to the Restrictions class via the Base/Virtual ORM services (getRestrictions()), or the Criteria object itself has a public property called restrictions which you can use rather easily. We prefer the latter approach. Now, please understand that the ColdBox Criteria Builder masks all this complexity and in very rare cases will you be going to our restrictions class directly. Most of the time you will just be happily concatenating methods on the Criteria Builder.

    Ok, now that the formalities have been explained let's build some criterias.

    Building Restrictions

    To build our criteria queries we will mostly use the methods in the criteria object or go directly to the restrictions object for very explicit criterions as explained above. We will also go to the restrictions object when we do conjunctions and disjunctions, which are fancy words for AND's, OR's and NOT's. So to build criterias we will be calling these criterion methods and concatenate them in order to form a nice DSL language that describes what we will retrieve. Once we have added all the criteria then we can use several other concatenated methods to set executions options and then finally retrieve our results or do projections on our results.

    Adobe ColdFusion may throw an "Invalid CFML construct" error for certain CBORM methods that match , such as .and(), .or(), and .eq(). You can use .$and(), .$or(), and .isEq() to avoid these errors and build cross-engine compatible code.

    In some cases (isEq(), isIn(), etc), you may receive data type mismatch errors. These can be resolved by using JavaCast on your criteria value or use our auto caster methods: idCast(), autoCast()

    You can also use the add() method to add a manual restriction or array of restrictions to the criteria you are building.

    But as you can see from the code, the facade methods are much nicer.

    Dynamic Negation

    Every restriction method you see above or in the docs can also be negated very easily by just prefixing the method with a not .

    Fluent If Statements - when()

    There comes times where you need some if statements in order to add criterias based on incoming input. That's ok, but we can do better by adding a when( test, target ) function that will evaluate the test argument or expression. If it evaluates to true then the target closure is called for you with the criteria object so you can do your criterias:

    c.eqProperty("createDate","modifyDate");

    eq(property, value) or isEq(property,value)

    Where a property equals a particular value, you can also use eq()

    c.eq("age",30);

    gt(property, value) or isGT(property, value)

    Where a property is greater than a particular value, you can also use gt()

    c.gt("publishedDate", now() );

    gtProperty(property,otherProperty)

    Where a one property must be greater than another

    c.gtProperty("balance","overdraft");

    ge(property,value) or isGE

    Where a property is greater than or equal to a particular value, you can also use ge()

    c.ge("age",18);

    geProperty(property, otherProperty)

    Where a one property must be greater than or equal to another

    c.geProperty("balance","overdraft");

    idEQ(required any propertyValue)

    Where an objects id equals the specified value

    c.idEq( 4 );

    ilike(required string property, required string propertyValue)

    A case-insensitive 'like' expression

    c.ilike("lastName", "maj%");

    isIn(required string property, required any propertyValue) or in(required string property, required any propertyValue)

    Where a property is contained within the specified list of values, the property value can be a collection (struct) or array or list, you can also use in()

    c.isIn( "id", [1,2,3,4] );

    isEmpty(required string property)

    Where a collection property is empty

    c.isEmpty("childPages");

    isNotEmpty(required string property)

    Where a collection property is not empty

    c.isNotEmpty("childPages");

    isFalse(required string property)

    Where a collection property is false

    c.isFalse("isPublished");

    isNull(required string property)

    Where a property is null

    c.isNull("passwordProtection");

    isNotNull(required string property)

    Where a property is NOT null

    c.isNotNull("publishedDate");

    islt(required string property, required any propertyValue) or lt()

    Where a property is less than a particular value, you can also use lt()

    c.isLT("age", 40 );

    ltProperty(required string property, required string otherProperty)

    Where a one property must be less than another

    c.ltProperty("sum", "balance");

    isle(required string property, required any propertyValue) or le()

    Where a property is less than or equal a particular value, you can also use le()

    c.isLE("age", 30);

    leProperty(required string property, required string otherProperty)

    Where a one property must be less than or equal to another

    c.LeProperty("balance","balance2");

    like(required string property, required string propertyValue)

    Equivalent to SQL like expression

    c.like("content", "%search%");

    ne(required string property, required any propertyValue)

    Where a property does not equal a particular value

    c.ne("isPublished", true);

    neProperty(required string property, required any otherProperty)

    Where one property does not equal another

    c.neProperty("password","passwordHash");

    sizeEq(required string property, required any propertyValue)

    Where a collection property's size equals a particular value

    c.sizeEq("comments",30);

    sizeGT(required string property, required any propertyValue)

    Where a collection property's size is greater than a particular value

    c.sizeGT("children",5);

    sizeGE(required string property, required any propertyValue)

    Where a collection property's size is greater than or equal to a particular value

    c.sizeGE("children", 10);

    sizeLT(required string property, required any propertyValue)

    Where a collection property's size is less than a particular value

    c.sizeLT("childPages", 25 );

    sizeLE(required string property, required any propertyValue)

    Where a collection property's size is less than or equal a particular value

    c.sizeLE("childPages", 25 );;

    sizeNE(required string property, required any propertyValue)

    Where a collection property's size is not equal to a particular value

    c.sizeNE("childPages",0);

    sql(required sql, params)

    Use arbitrary SQL to modify the resultset

    c.sql("char_length( lastName ) = 10");

    and(Criterion, Criterion, ...)

    Return the conjuction of N expressions as arguments

    c.and( c.restrictions.eq("name","luis"), c.restrictions.gt("age",30) );

    or(Criterion, Criterion, ….)

    Return the disjunction of N expressions as arguments

    c.or( c.restrictions.eq("name","luis"), c.restrictions.eq("name", "joe") )

    not(required any criterion) or isNot()

    Return the negation of an expression

    c.isNot( c.restrictions.eg("age", 30) );

    isTrue(required string property)

    Returns if the property is true

    c.isTrue("isPublished");

    Method

    Description

    Example

    between(property,minValue,maxValue)

    Where the property value is between two distinct values

    c.between("age",10,30);

    conjunction(required array restrictionValues)

    Group expressions together in a single conjunction (A and B and C...) and return the conjunction

    c.conjunction( [ c.restrictions.between("balance",100,200), c.restrictions.lt("salary",20000) ] );

    disjunction(required array restrictionValues)

    Group expressions together in a single disjunction (A or B or C...)

    c.disjunction( [ c.restrictions.between("balance",100,200), c.restrictions.lt("salary",20000) ] );

    eqProperty(property, otherProperty)

    https://docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/criterion/Restrictions.html
    reserved operator names

    Where one property must equal another

    // From base ORM service
    var restrictions = service.getRestrictions()
    
    // From Criteria Builder
    newCriteria().restrictions
    c.isEq("userID", idCast( 3 ) );
    c.add( c.restrictions.eq("name","luis") )
    c
        .notEq("userID", idCast( 3 ) )
        .notBetween()
        .notIsTrue()
        .notIsFalse();
    newCriteria()
        .when( isBoolean( arguments.isPublished ), function( c ){
            // Published process
            c
                .isEq( "isPublished", isPublished )
                .when( isPublished, function( c ){
                    c.isLt( "publishedDate", now() )
                    .$or( 
                        c.restrictions.isNull( "expireDate" ), 
                        c.restrictions.isGT( "expireDate", now() ) 
                    )
                    .isEq( "passwordProtection","" );
                })
            }
        } )
      .when( !isNull( arguments.showInSearch ), function( c ){
              c.isEq( "showInSearch", showInSearch );
       } )
       .when( arguments.isActive, function( c ){
                c.isTrue( "isActive" );
       })
      .list()

    Basic Crud - ActiveEntity

    Let's do a basic example of how to work with cborm when doing basic CRUD (Create-Read-Update-Delete). We will generate a ColdBox App, connect it to a database and leverage ActiveEntity for a nice quick CRUD App.

    The source code for this full example can be found in Github: or in ForgeBox:

    ColdBox App

    Let's start by creating a ColdBox app and preparing it for usage with ORM:

    Setup Environment

    Season the environment file (.env) with your database credentials and make sure that database exists:

    Setup ORM

    Now open the Application.cfc and let's configure the ORM by adding the following in the pseudo constructor and adding two lines of code to the request start so when we reinit the APP we can also reinit the ORM.

    To change the datasource name to something you like then update it here and in the .cfconfig.json file. Once done, issue a server restart and enjoy your new datasource name.

    Start Server

    Let's start a server and start enjoying the fruits of our labor:

    If you get a Could not instantiate connection provider: org.lucee.extension.orm.hibernate.jdbc.ConnectionProviderImpl error on startup here. It means that you hit the stupid Lucee bug where on first server start the ORM is not fully deployed. Just issue a server restart to resolve this.

    Create Entity - Person.cfc

    Let's start by creating a Person object with a few properties, let's use CommandBox for this and our super duper coldbox create orm-entity command:

    This will generate the models/Person.cfc as an ActiveEntity object and even create the unit test for it.

    Setup for BDD

    Since we love to promote tests at Ortus, let's configure our test harness for ORM testing. Open the /tests/Application.cfc and add the following code to setup the ORM and some functions for helping us test.

    Now that we have prepared the test harness for ORM testing, let's test out our Person with a simple unit test. We don't over test here because our integration test will be more pragmatic and cover our use cases:

    Basic CRUD

    We will now generate a handler and do CRUD actions for this Person:

    This creates the handlers/persons.cfc with the CRUD actions and a nice index action we will use to present all persons just for fun!

    Please note that this also generates the integrations tests as well under /tests/specs/integration/personsTest.cfc

    Create

    We will get an instance of a Person, populate it with data and save it. We will then return it as a json memento. The new() method will allow you to pass a struct of properties and/or relationships to populate the new Person instance with. Then just call the save() operation on the returned object.

    You might be asking yourself: Where does this magic getMemento() method come from? Well, it comes from the mementifier module wich inspects ORM entities and injects them with this function to allow you to produce raw state from entities. (Please see: https://forgebox.io/view/mementifier)

    Read

    We will get an instance according to ID and show it's memento in json. There are many ways in the ORM service and Active Entity to get objects by criteria,

    In this example, we use the get() method which retrieves a single entity by identifier. Also note the default value of 0 used as well. This means that if the incoming id is null then pass a 0. The orm services will detect the 0 and by default give you a new Person object, the call will not fail. If you want your call to fail so you can show a nice exception for invalid identifiers you can use getOrFail() instead.

    Update

    Now let's retrieve an entity by Id, update it and save it again!

    Delete

    Now let's delete an incoming entity identifier

    Note that you have two choices when deleting by identifier:

    1. Get the entity by the ID and then send it to be deleted

    2. Use the deleteById() and pass in the identifier

    The latter allows you to bypass any entity loading, and do a pure HQL delete of the entity via it's identifier. The first option is more resource intensive as it has to do a 1+ SQL calls to load the entity and then a final SQL call to delete it.

    List All

    For extra credit, we will get all instances of Person and render their memento's

    That's it! We are now rolling with basic CRUD cborm style!

    BDD Tests

    Here are the full completed BDD tests as well

    https://github.com/coldbox-samples/cborm-crud-demo
    https://forgebox.io/view/cborm-crud-demo
    # Create folder
    mkdir myapp --cd
    # Scaffold App
    coldbox create app
    # Install cborm, dotenv, and cfconfig so we can get the CFML engine talking to the DB fast.
    install cborm,commandbox-dotenv,commandbox-cfconfig
    # Update the .env file
    cp .env.example .env
    # ColdBox Environment
    APPNAME=ColdBox
    ENVIRONMENT=development
    
    # Database Information
    DB_CONNECTIONSTRING=jdbc:mysql://127.0.0.1:3306/cborm?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useLegacyDatetimeCode=true
    DB_CLASS=com.mysql.jdbc.Driver
    DB_DRIVER=MySQL
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=cborm
    DB_USER=root
    DB_PASSWORD=cborm
    
    # S3 Information
    S3_ACCESS_KEY=
    S3_SECRET_KEY=
    S3_REGION=us-east-1
    S3_DOMAIN=amazonaws.com
    Application.cfc
    // Locate the cborm module for events
    this.mappings[ "/cborm" ] = COLDBOX_APP_ROOT_PATH & "modules/cborm";
    
    // The default dsn name in the ColdBox scaffold
    this.datasource = "coldbox"; 
    // ORM Settings + Datasource
    this.ormEnabled = "true";
    this.ormSettings = {
    	cfclocation = [ "models" ], // Where our entities exist
    	logSQL = true, // Remove after development to false.
    	dbcreate = "update", // Generate our DB
    	automanageSession = false, // Let cborm manage it
    	flushAtRequestEnd = false, // Never do this! Let cborm manage it
    	eventhandling = true, // Enable events
    	eventHandler = "cborm.models.EventHandler", // Who handles the events
    	skipcfcWithError = true // Yes, because we must work in all CFML engines
    };
    
    // request start
    public boolean function onRequestStart( string targetPage ){
    	// If we reinit our app, reinit the ORM too
    	if( application.cbBootstrap.isFWReinit() )
    		ormReload();
    	
    	// Process ColdBox Request
    	application.cbBootstrap.onRequestStart( arguments.targetPage );
    
    	return true;
    }
    # Start a default Lucee Server
    server start
    coldbox create orm-entity 
        entityName="Person"
        activeEntity=true
        properties=name,age:integer,lastVisit:timestamp
    Person.cfc
    /**
     * A cool Person entity
     */
    component persistent="true" table="Person" extends="cborm.models.ActiveEntity"{
    
    	// Primary Key
    	property name="id" fieldtype="id" column="id" generator="native" setter="false";
    	
    	// Properties
    	property name="name" ormtype="string";
    	property name="age" ormtype="numeric";
    	property name="lastVisit" ormtype="timestamp";
    	
    	// Validation
    	this.constraints = {
    		// Example: age = { required=true, min="18", type="numeric" }
    	};
    	
    	// Constructor
    	function init(){
    		super.init( useQueryCaching="false" );
    		return this;
    	}
    }
    /tests/Application.cfc
    	// Locate the cborm module for events
    	this.mappings[ "/cborm" ] = rootPath & "modules/cborm";
    
    	// ORM Settings + Datasource
    	this.datasource = "coldbox"; // The default dsn name in the ColdBox scaffold
    	this.ormEnabled = "true";
    	this.ormSettings = {
    		cfclocation = [ "models" ], // Where our entities exist
    		logSQL = true, // Remove after development to false.
    		dbcreate = "update", // Generate our DB
    		automanageSession = false, // Let cborm manage it
    		flushAtRequestEnd = false, // Never do this! Let cborm manage it
    		eventhandling = true, // Enable events
    		eventHandler = "cborm.models.EventHandler", // Who handles the events
    		skipcfcWithError = true // Yes, because we must work in all CFML engines
    	};
    
    	public boolean function onRequestStart( string targetPage ){
    		ormReload();
    		return true;
    	}
    /tests/specs/unit/PersonTest.cfc
    component extends="coldbox.system.testing.BaseTestCase"{
    
    	function run(){
    		describe( "Person", function(){
    			it( "can be created", function(){
    				expect( getInstance( "Person" ) ).toBeComponent()
    			});
    		});
    	}
    
    }
    coldbox create handler 
        name="persons" 
        actions="index,create,show,update,delete" 
        views=false
    /**
     * create a person
     */
    function create( event, rc, prc ){
    	prc.person = getInstance( "Person" )
    		.new( {
    			name 	: "Luis",
    			age 	: 40,
    			lastVisit : now()
    		} )
    		.save();
    	return prc.person.getMemento( includes="id" );
    }
    /**
     * show a person
     */
    function show( event, rc, prc ){
    	return getInstance( "Person" )
    		.get( rc.id ?: 0 )
    		.getMemento( includes="id" );
    }
    /**
     * show a person
     */
    function show( event, rc, prc ){
    	return getInstance( "Person" )
    		.getOrFail( rc.id ?: -1 )
    		.getMemento( includes="id" );
    }
    /**
     * Update a person
     */
    function update( event, rc, prc ){
    	prc.person = getInstance( "Person" )
    		.getOrFail( rc.id ?: -1 )
    		.setName( "Bob" )
    		.save();
    	return prc.person.getMemento( includes="id" );
    }
    /**
     * Delete a Person
     */
    function delete( event, rc, prc ){
    	try{
    		getInstance( "Person" )
    			.getOrFail( rc.id ?: '' )
    			.delete();
    		// Or use the shorthnd notation which is faster
    		// getIntance( "Person" ).deleteById( rc.id ?: '' )
    	} catch( any e ){
    		return "Error deleting entity: #e.message# #e.detail#";
    	}
    
    	return "Entity Deleted!";
    }
    /**
     * List all Persons
     */
    function index( event, rc, prc ){
    	return getInstance( "Person" )
    		// List all as array of objects
    		.list( asQuery=false )
    		// Map the entities to mementos
    		.map( function( item ){
    			return item.getMemento( includes="id" );
    		} );
    }
    /tests/specs/integration/personsTest.cfc
    component extends="coldbox.system.testing.BaseTestCase" appMapping="/"{
    
    	function run(){
    
    		describe( "persons Suite", function(){
    
    			aroundEach( function( spec ) {
    				setup();
    				transaction{
    					try{
    						arguments.spec.body();
    					} catch( any e ){
    						rethrow;
    					} finally{
    						transactionRollback();
    					}
    				}
    		   	});
    
    			it( "index", function(){
    				var event = this.GET( "persons.index" );
    				// expectations go here.
    				expect( event.getRenderedContent() ).toBeJSON();
    			});
    
    			it( "create", function(){
    				var event = this.POST(
    					"persons.create"
    				);
    				// expectations go here.
    				var person = event.getPrivateValue( "Person" );
    				expect( person ).toBeComponent();
    				expect( person.getId() ).notToBeNull();
    			});
    
    			it( "show", function(){
    				// Create mock
    				var event = this.POST(
    					"persons.create"
    				);
    				// Retrieve it
    				var event = this.GET(
    					"persons.show", {
    						id : event.getPrivateValue( "Person" ).getId()
    					}
    				);
    				// expectations go here.
    				var person = event.getPrivateValue( "Person" );
    				expect( person ).toBeComponent();
    				expect( person.getId() ).notToBeNull();
    			});
    
    			it( "update", function(){
    				// Create mock
    				var event = this.POST(
    					"persons.create"
    				);
    				var event = this.POST(
    					"persons.update", {
    						id : event.getPrivateValue( "Person" ).getId()
    					}
    				);
    				// expectations go here.
    				var person = event.getPrivateValue( "Person" );
    				expect( person ).toBeComponent();
    				expect( person.getId() ).notToBeNull();
    				expect( person.getName() ).toBe( "Bob" );
    			});
    
    			it( "delete", function(){
    				// Create mock
    				var event = this.POST(
    					"persons.create"
    				);
    				// Create mock
    				var event = this.DELETE(
    					"persons.delete", {
    						id : event.getPrivateValue( "Person" ).getId()
    					}
    				);
    				expect( event.getRenderedContent() ).toInclude( "Entity Deleted" );
    			});
    
    		});
    
    	}
    
    }
    

    Overview

    The cborm module will enhance your ORM Entities and ColdBox application by providing you with features in the following areas:

    • Active Record Pattern

      • You can extend your entities from our ActiveEntity class and take advantage of both Active Record and Hibernate ORM

    • Entity Population

      • Easily populate entities from json, structs, xml, queries and build up even the entity relationships from flat data.

    • Entity Marshalling to raw data types ()

      • Easily extract the data from entities and their relationships so you can marshall them to json, xml, etc.

    • Automatic CRUD Resource Handler

      • If you extend our cborm.models.resources.BaseHandler it will generate the full CRUD for a specific entity based on ColdBox Resources

    • ORM Events

      • Easily listen to multiple ORM events via ColdBox Interceptors

    • Service Layers

      • Enhance the ability to list, query, find entities, work with native hibernate constructs and more.

    • Validation

      • We provide you with a unique validator to validate against unique columns

    Just write your entities and their relationships and we will take care of the rest!

    Base ORM Service

    Let's begin our adventure with the BaseORMService model. This model can be injected or requested via WireBox and will be used to interact with any entity in our system or with Hibernate directly:

    This service object acts as an abstraction layer to the ColdFusion ORM (Hibernate) and can work with any entity in your system as all methods most likely receive the entityName argument. You will be able to do the following category of actions from this service class:

    • Hibernate Session utility methods

    • Entity metadata methods

    • Querying methods

    • Criteria Queries or fluent SQL

    This means that you don't need to create a service layer CFC in order to work with ORM entities, you can leverage this abstraction to work with your ORM needs. You can also specifically bind (root) the service to a specific entity, which we lovingly call a VirtualEntityService. This way you don't have to be passing the entity names left and right, the virtual entity service will be constructed with the name and all operations will be done upon that entity.

    Example

    Once you have access to the injected base ORM service, you can use it in all of its glory.

    Important Please check out the latest API Docs for the latest methods and functionality:

    What is this asStream() call? What are Streams?

    A stream is an abstraction, it’s not a data structure. It’s not a collection where you can store elements. The most important difference between a stream and a structure is that a stream doesn’t hold the data. For example you cannot point to a location in the stream where a certain element exists. You can only specify the functions that operate on that data. A stream is an abstraction of a non-mutable collection of functions applied in some order to the data.

    More information can be found here:

    Virtual Services

    We also have a virtual service layer that can be mapped to specific entities and create entity driven service layers virtually. Meaning you don't have to be passing any entity names to the API methods to save you precious typing time. This is achieved via the VirtualEntityService model which inherits from the BaseORMService class.

    You can achieve this in several manners:

    • Injection

      • entityService:{EntityName}

    • Request via WireBox using the DSL argument of getInstance()

    That's it! You can use it just like the BaseORMService except no more passing the entity name.

    Example

    Concrete Services

    This is where you create your own CFC that inherits from our VirtualEntityService and either adds or overrides methods. The virtual and base services takes you about 90% of the way. With you concrete services, you can complete the functionality to your liking.

    All you need to do is inherit from the cborm.models.VirtualEntityService and call the parent class constructor with the available arguments:

    • entityname - The name of the entity to root this service with (REQUIRED)

    • queryCacheRegion - The name of the query cache region if using caching, defaults to #arguments.entityName#.defaultVSCache

    • useQueryCaching

    Active Entity

    If you want to apply an Active Record and fluent feel to your entities then ActiveEntity is just for you. Just inherit from cborm.models.ActiveEntity and you are on your way to Active Record bliss.

    ActiveEntity inherits from the VirtualEntityService class which inherits from the BaseORMService class. So you have the full gamut of usage plus the ability for the active entity to validate itself. It has the isValid() and getValidationResults() methods to help you with the validation of a populated entity.

    Example Entity

    Example Usage

    Automatic ORM Resource Handler

    If you are creating RESTful services, you can leverage our new Base ORM Handler that will give you a full CRUD service for your entities. All you have to do is the following:

    1. Create your entities

      1. Add mementifier data ()

      2. Add validation data ()

    That's it! This handler will now manage ALL the CRUD operations in REST format for your entity including relationships, validations, pagination and data marshalling.

    Getters

  • Finders

  • Dynamic Finders

  • Counters

  • Dynamic Counters

  • Persistence (save,update,delete) and bulk persistence with transactions

  • Eviction Methods

  • Population Methods

  • getInstance( dsl = entityService:{EntityName} );

  • Request via a Base ORM Service

    • createService()

  • - Activate query caching, defaults to false
  • eventHandling - Activate event handling, defaults to true

  • useTransactions - Activate transaction blocks on calls, defaults to true

  • defaultAsQuery - Return query or array of objects on list(), executeQuery(), criteriaQuery(), defaults to true

  • datasource - The datasource name to be used for the rooted entity, if not we use the default datasource

  • Register the resource in your application router or module router
    1. resources( "users" )

  • Create the handler that will manage that resource and extend our base handler, spice up as needed and you are done:

  • mementifier
    https://apidocs.ortussolutions.com/#/coldbox-modules/cborm/
    https://forgebox.io/view/cbstreams
    https://forgebox.io/view/mementifier
    https://forgebox.io/view/cbvalidation
    ColdBox Resources
    // inject via dsl
    property name="ormService" inject="entityService";
    // inject via alias
    property name="ormService" inject="baseORMService@cborm";
    
    // use via alias
    getInstance( "BaseORMService@cborm" );
    // use via dsl
    getInstance( dsl="entityService" );
    component{
    
        inject name="ormService" inject="entityService";
    
        function saveUser( event, rc, prc ){
            // retrieve and populate a new user object
            var user = populateModel( ormService.new( "User" ) );
    
            // save the entity using hibernate transactions
            ormService.save( user );
    
            relocate( "user.list" );
        }
    
        function list( event, rc, prc ){
    
            // get a listing of all users with pagination and filtering
            prc.users = ormService.list(
                entityName = "User",
                criteria   = { isActive : true },
                sortOrder  = "fname",
                offset     = event.getValue( "startrow", 1 ),
                max         = 20
            );
    
            event.setView( "user/list" );
        }
    
        // Dynamic Finders
        function findUsers( event, rc, prc ){
            prc.data = ormService.findByRoleAndIsActive( "User", "Admin", true );
        }
    
        // Fluent Criteria Queries
        function searchContent( event, rc, prc ){
            prc.dataStream = ormService
                .newCriteria( "Content" )
                    .isEq( "published", rc.isPublished  )
                    .isLt( "publishedDate", now() )
                    .or( 
                        ormService.getRestrictions().isNull( "expireDate" ), 
                        ormService.getRestrictions().isGT( "expireDate", now() ) 
                    )
                    .isEq( "passwordProtection", "" )
                    .joinTo( "activeContent", "ac" )
                        .like( "title", "%#rc.searchTerm#%" )
                .asStream()
                .list( offset=rc.offset, max=50 );
    
            event.setView( "content/search")
        }
    
    }
    // Injection
    property name="userService" inject="entityService:User"
    
    // Request it
    contentService = getInstance( dsl = "entityService:Content" );
    
    // Via ORM Service
    ormService.createService( entityName="Content", useQueryCaching=true );
    component{
    
        inject name="userService" inject="entityService:User";
    
        function saveUser( event, rc, prc ){
            // retrieve and populate a new user object
            var user = populateModel( userService.new() );
    
            // save the entity using hibernate transactions
            userService.save( user );
    
            relocate( "user.list" );
        }
    
        function list( event, rc, prc ){
    
            // get a listing of all users with pagination and filtering
            prc.users = userService.list(
                criteria   = { isActive : true },
                sortOrder  = "fname",
                offset     = event.getValue( "startrow", 1 ),
                max         = 20
            );
    
            event.setView( "user/list" );
        }
    
        // Dynamic Finders
        function findUsers( event, rc, prc ){
            prc.data = userService.findByRoleAndIsActive( "Admin", true );
        }
    
        // Criteria Queries
        function searchContent( event, rc, prc ){
            prc.dataStream = userService
                .newCriteria()
                    .isEq( "published", rc.isPublished  )
                    .isLt( "publishedDate", now() )
                    .or( 
                        ormService.getRestrictions().isNull( "expireDate" ), 
                        ormService.getRestrictions().isGT( "expireDate", now() ) 
                    )
                    .isEq( "passwordProtection", "" )
                    .joinTo( "activeContent", "ac" )
                        .like( "title", "%#rc.searchTerm#%" )
                .asStream()
                .list( offset=rc.offset, max=50 );
    
            event.setView( "content/search")
        }
    
    }
    models/ContentService.cfc
    component extends="cborm.models.VirtualEntityService" singleton{
    
        /**
         * Constructor
         * @entityName The content entity name to bind this service to.
         */
        ContentService function init( entityName="cbContent" ){
            // init it
            super.init( entityName=arguments.entityName, useQueryCaching=true );
    
            return this;
        }
    
    }
    models/User.cfc
    component persistent="true" table="users" extends="cborm.models.ActiveEntity"{
    
        property name="id" column="user_id" fieldType="id" generator="uuid";
    
        /**
         * @display First Name
         * @message Please provide firstname
         * @NotEmpty
         */
        property name="firstName";
    
        /**
         * @display Last Name
         * @message Please provide lastname
         * @NotEmpty
         */
        property name="lastName";
        property name="userName";
        property name="password";
        property name="lastLogin" ormtype="date";
    
        // M20 -> Role
        property name="role" cfc="Role" fieldtype="many-to-one" fkcolumn="FKRoleID" lazy="true" notnull="false";
    
        // DI Test
        property name="wirebox" inject="wirebox" persistent="false";
    
        // Constraints
        this.constraints = {
            firstName = { required=true }, 
            lastName  = { required=true }, 
            username  = { required=true, min=5, validator="UniqueValidator@cborm" }, 
            password  = { required=true, min=6 }
        };
    
    }
    user = entityNew( "User" ).get( 2 );
    var isValid = entityNew( "User" )
        .populate( memento=rc, composeRelationships=true )
        .isValid();
    
    user = entityNew( "User" ).findAllByIsActive( true );
    user = entityNew( "User" )
        .get( 4 )
        .setName( "Awesome" )
        .save();
    
    entityNew( "User" )
        .getOrFail( 4 )
        .delete();
    
    prc.users = entityNew( "User" )
        .findAllWhere( 
           criteria = { isActive:true, role:entityNew( "Role" ).findByName( "Admin" ) },
           stream = true
        );
    handlers/users.cfc
    /**
    * My awesome cb6 resources handler
    */
    component extends="cborm.models.resources.BaseHandler"{
    
        // Inject the correct service as the `ormService` for the resource Handler
        property name="ormService" inject="entityService:Role";
    
        // The default sorting order string: permission, name, data desc, etc.
        variables.sortOrder = "name";
        // The name of the entity this resource handler controls. Singular name please.
        variables.entity    = "Role";
    
    }