Re: Horizontal inheritance to splitting big database tables to speed up?
Re: Horizontal inheritance to splitting big database tables to speed up?
- Subject: Re: Horizontal inheritance to splitting big database tables to speed up?
- From: Jean-François Veillette <email@hidden>
- Date: Mon, 18 Jul 2016 06:58:09 -0400
> On Jul 18, 2016, at 6:33 AM, Jean-François Veillette <email@hidden> wrote:
>
>
>> No other (reasonably simple) way you would know of to hook into EOF to obtain the desired effect, i.e., to be able to determine the target entity _before_ a fetch?
>
> I have used such mechanism to good results. It was largely inspired by ERXLongPrimaryKeyFactory, it worked well.
>
> I could dig and find that code … if you ask for it ;-)
>
> jfv
Found it !
…
package a.b.c.d;
import java.util.Hashtable;
import java.util.Stack;
import org.apache.log4j.Logger;
import com.webobjects.eoaccess.EOAdaptorChannel;
import com.webobjects.eoaccess.EODatabaseContext;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOModelGroup;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.eocontrol.EOObjectStoreCoordinator;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import er.extensions.eof.ERXEC;
import er.extensions.eof.ERXEOAccessUtilities;
import er.extensions.eof.ERXModelGroup;
import er.extensions.foundation.ERXProperties;
/**
* <h3>Largely inspired by ERXLongPrimaryKeyFactory (original author: email@hidden)</h3><br/>
* Comments below come from ERXLongPrimaryKeyFactory but were modified to reflect code changes.<br/>
* <br/>
* Automatically generates Long primary keys for entities. Features a cache which reduces database round trips as well as encoding Entity type in PK value.<br/>
* <br/>
* <h3>usage:</h3><br/>
* <br/>
* override either the ERXGeneratesPrimaryKey interface like this:<br/>
* <code><pre>
* private NSDictionary _primaryKeyDictionary = null;
*
* public NSDictionary primaryKeyDictionary(boolean inTransaction) {
* if (_primaryKeyDictionary == null) {
* _primaryKeyDictionary = EntityEncodedPrimaryKeyFactory.primaryKeyDictionary(this);
* }
* return _primaryKeyDictionary;
* }
* </pre>
* </code><br/>
* or manually call<br/>
* <code>EntityEncodedPrimaryKeyFactory.primaryKeyDictionary(EOEnterpriseObject eo);</code><br/>
* <br/>
* the necessary database table is generated on the fly.<br/>
* <br/>
* <h3>Encoding Entity in PK values</h3><br/>
* The first 10 bits from the 64 bits primary key is used to encode the Subentity in the pk value. This speeds up the inheritance with multiple tables. In order to support this you must add an entry to the userInfo from the Subentities:<br/>
* <br/>
* <code>key=entityCode</code><br/>
* <code>value= %lt;%lt; an unique integer, no longer than 10 bit - 1</code><br/>
* <h3>Entity Qualifier</h3><br/>
* Using the first 10 bits also mean that for a single entity, the primary key will always be within a specific range. <br/>
* For example, let's say the entityCode = 1, which means that the primary key will be greater than or equal to (1<<54) and less than or equal to ((2<<54) - 1).
*
* <pre>
* 1L << 54L = (long) 18 014 398 509 481 984
* 2L << 54L = (long) 36 028 797 018 963 968
* </pre>
*
* This leave 54 bits to encode the sequence within each range.<br/>
* Adjust accordingly, based on your use case.
*/
public class EntityEncodedPrimaryKeyFactory {
private static final int CODE_LENGTH = 10;
private static final int SEQ_LENGTH = Long.SIZE - CODE_LENGTH;
private static final Logger log = Logger.getLogger(EntityEncodedPrimaryKeyFactory.class);
private static long MAX_PK_VALUE = (long) Math.pow(2, SEQ_LENGTH);
private final Hashtable<String, Stack<Long>> pkCache = new Hashtable<String, Stack<Long>>();
private Integer increaseBy;
private Long getNextPkValueForEntity(String ename) {
long seq = cachedPkValue(ename);
if (seq > MAX_PK_VALUE) {
throw new IllegalStateException("max PK value reached for entity " + ename + " cannot continue!");
}
// now add the entity code
long realPk = baseForEntityName(ename) + seq;
if (log.isDebugEnabled()) {
log.debug("new pk value for " + ename + "(" + ((ERXModelGroup) EOModelGroup.defaultGroup()).entityCode(ename) + "), db value = " + seq + ", new value = " + realPk);
}
return realPk;
}
public long baseForEntityName(String ename) {
long entityCode = ((ERXModelGroup) EOModelGroup.defaultGroup()).entityCode(ename);
return entityCode << SEQ_LENGTH;
}
public int entityCodeFromPrimaryKey(long key) {
return (int) (key >> SEQ_LENGTH);
}
/*
* @see com.webobjects.eoaccess.EOModelGroup.Delegate#subEntityForEntity(com.webobjects.eoaccess.EOEntity, com.webobjects.foundation.NSDictionary)
*/
@SuppressWarnings("unchecked")
public EOEntity subEntityForEntity(EOEntity entity, NSDictionary<String, ?> pkDict) {
//get the code, we assume that the pkDict contains only one pk value!
NSArray<?> values = pkDict.allValues();
if (values.count() > 1) {
throw new IllegalArgumentException("subEntityForEntity in its default implementation" + " works only with single pk long values " + entity.name() + " has " + pkDict);
}
long pkValueWithCode;
Object val = values.objectAtIndex(0);
try {
pkValueWithCode = ((Number) val).longValue();
} catch (ClassCastException e) {
throw new IllegalArgumentException("subEntityForEntity in its default implementation" + " works only with single pk long values, expected a java.lang.Number but got a " + val);
}
// remove the entity base
int entityCode = entityCodeFromPrimaryKey(pkValueWithCode);
// find the one with that code
for (EOEntity subEntity : (NSArray<? extends EOEntity>) entity.subEntities()) {
if (((ERXModelGroup) EOModelGroup.defaultGroup()).entityCode(subEntity) == entityCode) {
return subEntity;
}
}
return null;
}
public synchronized static Object primaryKeyValue(String entityName) {
return factory().primaryKeyDictionary(entityName).objectEnumerator().nextElement();
}
public synchronized static NSDictionary<String, Object> primaryKeyDictionary(EOEnterpriseObject eo) {
String entityName = eo.entityName();
return factory().primaryKeyDictionary(entityName);
}
private static EntityEncodedPrimaryKeyFactory _factory;
private static EntityEncodedPrimaryKeyFactory factory() {
if (_factory == null) {
_factory = new EntityEncodedPrimaryKeyFactory();
EOModelGroup.defaultGroup().setDelegate(_factory);
}
return _factory;
}
private NSDictionary<String, Object> primaryKeyDictionary(final String entityName) {
EOEntity entity = EOModelGroup.defaultGroup().entityNamed(entityName);
while (entity.parentEntity() != null) {
entity = entity.parentEntity();
}
String rootEntityName = entity.name();
if (entity.primaryKeyAttributeNames().count() != 1) {
throw new IllegalArgumentException("Can handle only entities with one PK: " + rootEntityName + " has " + entity.primaryKeyAttributeNames());
}
Long pk = getNextPkValueForEntity(entityName);
String pkName = (String) entity.primaryKeyAttributeNames().objectAtIndex(0);
return new NSDictionary<String, Object>(new Object[]{pk}, new String[]{pkName});
}
/**
* returns a new primary key for the specified entity.
*
* @param ename, the entity name for which this method should return a new primary key
* @param count, the number of times the method should try to get a value from the database if something went wrong (a deadlock in the db for example -> high traffic with multiple instances)
* @param increaseBy, if > 1 then the value in the database is increased by this factor. This is useful to 'get' 10000 pk values at once for caching. Removes a lot of db round trips.
* @return a new pk values for the specified entity.
*/
private Long getNextPkValueForEntityIncreaseBy(String entityName, int increasePkBy) {
int step = (increasePkBy < 1) ? 1 : increasePkBy;
EOEditingContext ec = ERXEC.newEditingContext();
ec.lock();
try {
EODatabaseContext dbc = ERXEOAccessUtilities.databaseContextForEntityNamed((EOObjectStoreCoordinator) ec.rootObjectStore(), entityName);
dbc.lock();
try {
EOEntity entity = ERXEOAccessUtilities.entityNamed(ec, entityName);
EOAdaptorChannel channel = (EOAdaptorChannel) dbc.adaptorContext().channels().lastObject();
NSArray<?> result = channel.primaryKeysForNewRowsWithEntity(step, entity);
Number n = (Number) ((NSDictionary<?, ?>) result.lastObject()).allValues().lastObject();
return n.longValue();
} catch (Exception e) {
throw new IllegalStateException("Couldn't get PK");
} finally {
dbc.unlock();
}
} finally {
ec.unlock();
}
}
/**
* Returns a new integer based PkValue for the specified entity. If the cache is empty, it is refilled again.
*
* @param ename, the entity name for which this method should return a new primary key
* @return a new Integer based primary key for the specified entity.
*/
private Long cachedPkValue(String ename) {
Stack<Long> s = cacheStack(ename);
if (s.empty()) {
synchronized (s) {
if (s.empty()) {
fillPkCache(s, ename);
}
}
}
Long pkValue = s.pop();
return pkValue;
}
/**
* Looks at the cache hashtable if there is already a Stack for the specified entity name. If there is no Stack, a new Stack object will be created.
*
* @param ename, the name of the entity for which this method should return the Stack
* @return the Stack with primary key values for the specified entity.
*/
private Stack<Long> cacheStack(String ename) {
Stack<Long> s = pkCache.get(ename);
if (s == null) {
s = new Stack<Long>();
pkCache.put(ename, s);
}
return s;
}
/**
* creates x primary key values for the specified entity and updates the database, where x is the number specified in increaseBy
*
* @param s, the stack into which the pk values should be inserted
* @param ename, the entity name for which the pk values should be generated
*/
private void fillPkCache(Stack<Long> s, String ename) {
Long pkValueStart = getNextPkValueForEntityIncreaseBy(ename, increaseBy());
long value = pkValueStart.longValue();
log.debug("filling pkCache for " + ename + ", starting at " + value);
for (int i = increaseBy(); i > 0; i--) {
s.push(Long.valueOf(i + value));
}
}
/**
* The number of cached keys, set the property <code>er.extensions.ERXLongPrimaryKeyFactory.increaseBy</code> to the interval you want to use.
*
* @return the interval to use for cached keys
*/
private int increaseBy() {
if (increaseBy == null) {
increaseBy = new Integer(ERXProperties.intForKeyWithDefault("er.extensions.ERXLongPrimaryKeyFactory.increaseBy", 1));
}
return increaseBy.intValue();
}
}
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Webobjects-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden