Home Getting Started Download Javadocs Discussion |
Getting StartedBrian Gorman |
BeanBin is a tool in order to make persisting EJB 3.0 entity beans easier. It eliminates the need to write any JPA-QL for normal tasks while being portable enough to use in any part of your application. BeanBin replaces your lazy association collections with its own so your application can lazily initialize outside of the container. Text index searching is fully integrated as well and BeanBin treats searching on them as if you were searching on a normal property.
BeanBin has only works currently with JBoss AS 4.0.4 with jboss-EJB-3.0_RC9-FD.
You can download the jboss-EJB-3.0_RC9-FD here.
The easiest way to install EJB 3.0 to your JBoss 4.0.4.GA install is unzip and run ant -f install -Djboss.home=/path/to/jboss -Djboss.server.config=all to install to the all server configuration.
You might want to update the access times inside of the jboss-EJB-3.0_RC9-FD folder to insure that some of the files the installer tries to copy will replace new access times for older jars in your JBoss directory.
The only pieces of configuration BeanBin requires you to set up has to do with the DataStore and what directory you want Lucene indexes to be stored.
Edit the beanbin-ds.xml file in your server's deploy directory and fill out all the required information for your DataStore.
Next, edit the beanbin.properties file in the config directory and supply the hiberate dialect flavor for your database vendor.
You can also set any other property here that you need to pass through to Hibernate's EntityManagerFactory creation.
Also, you need to edit the lucene.dir property to be somewhere on your filsystem you want Lucene to store text search files.
BeanBin's interface was designed to be as simple and as readable as possible. BeanBin is also implements Iterable so you can iterate over an entire object store by simply coding the following:
BeanBin<SomeEntity> bin = new BeanBin<SomeEntity>(SomeEntity.class);
for(SomeEntity entity : bin) {
// some operation
}
The only object that this BeanBin will pass back is SomeEntity. The use of generics enables BeanBin's interface to pass back the same type of List that the developer is looking for. The Class object representing SomeEntity is passed in order for BeanBin to determine the object structure and build an EntityManager on the fly based on that object structure.
BeanBin<SomeEntity> bin = new BeanBin<SomeEntity>(SomeEntity.class);
List<SomeEntity> list = bin.matches("someProperty", "some term");
The List implementation returned contains all of the matches!
BeanBin can be used anywhere within your code. It connects to an EJB3 stateless session bean using JNDI through an InitialContext. The developer can set his/her own InitialContext for BeanBin to use if the default constructor wont work. This will be discussed in more detail in the testing section.
BeanBin uses an arguement accumulator in order to build search criteria. If you are familiar with Hibernate, this is very similar to their criteria query API. This enables the developer to easily build complex queries that end up being extremely readable.
BeanBin<SomeEntity> bin = new BeanBin<SomeEntity>(SomeEntity.class);
List<SomeEntity> results = bin.lessThan("intProperty", 5)
.and()
.greaterThan("intProperty", 10)
.and()
.matches("someProperty", "*term*");
// query has not been submitted yet
for(SomeEntity entity : results) {
// query was submitted when iteration started
}
BeanBin actually returns its own implementation of java.util.List called ActiveList. ActiveList holds and builds the search criteria until any of java.util.List's methods are called. It then initializes the List by sending the query to the session bean only when it is actually being used.
Sorting with BeanBin is as easy as adding an additional criteria to the search.
BeanBin<SomeEntity> bin = new BeanBin<SomeEntity>(SomeEntity.class);
List<SomeEntity> results = bin.lessThan("intProperty", 5)
.and()
.greaterThan("intProperty", 10)
.sortBy("someProperty");
// query has not been submitted yet
for(SomeEntity entity : results) {
// query was submitted when iteration started
}
You can also specifiy the nature of the sort by using the .sortBy("someProperty", SortBy.DECENDING) method.
Below is an example of how BeanBin incorperates pagination.
BeanBin<SomeEntity> bin = new BeanBin<SomeEntity>(SomeEntity.class);
List<SomeEntity> results = bin.lessThan("intProperty", 5).fetchSize(25);
// query has not been submitted yet
for(SomeEntity entity : results) {
// Every 25 iterations an updated position gets submitted and populated
// Everything happens without you ever having to worry about it.
}
BeanBin implements its own form of lazy intialization for sub entities. It keeps a special collection which will populate itself through BeanBin's session bean whenever and whereever it is first used. This will stay transparent to the developer. An example with comments explaining what is going on behind the scenes is as follows:
BeanBin<SomeEntity> bin = new BeanBin<SomeEntity>(SomeEntity.class);
List<SomeEntity> results = bin.matches("property", "search term");
for(SomeEntity entity : results) {
List<SubEntity> subs = entity.getSubEntities();
// the lazy sub entities aren't initialized
for(SubEntity sub : subs) {
// subs just got initialized from session bean
}
}
I believe that the detachement strategy employed by EJB 3.0 is often annoying due to the fact quite frequently a project's buisness logic exists outside the context an EJB container. This means that whenever you try to access an unfetched lazy collection from outside a container JBoss will throw a LazyInitializationError. BeanBin fetches the uninitialized property behind the scenes no matter if you are inside the container or not.
You can make any java.lang.String property to be automatically indexed by BeanBin. You can also make any property that is a List automatically index as well. BeanBin will do the index searching automatically if an index property is included within a query. Check out the example below:
@Entity
public class SomeEntity {
private String someString;
private int someInt;
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@IndexSearch
public String getSomeString() {
return this.someString;
}
public void setSomeString(String string) {
this.someString = string;
}
public int getSomeInt() {
return this.someInt;
}
public void setSomeInt(int i) {
this.someInt = i;
}
@IndexSearch
@Transient
public List<String> getKeywords() {
ArrayList<String> keywords = new ArrayList<String>();
keywords.add(getSomeString());
keywords.add(getName());
return keywords;
}
}
Search on index properties just as you would any other property even if it is transient to the database!
BeanBin<SomeEntity> bin = new BeanBin<SomeEntity>(SomeEntity.class);
List<SomeEntity> results = bin.matches("keywords", "some keyword")
.and()
.greaterThan("someInt", 10);
Saving with BeanBin was intended to be as easy as possible.
BeanBin<SomeEntity> bin = new BeanBin<SomeEntity>(SomeEntity.class);
SomeEntity entity = new SomeEntity();
entity.setSomeString("some string");
entity.setSomeInt(5);
SomeOtherEntity other = new SomeOtherEntity();
other.setProperty("property");
entity.setSomeOther(other);
/* this will trigger an insert for SomeEntity AND SomeOtherEntity
with the correct key assignments */
bin.putIn(entity);
List<SomeEntity> list = bin.matches("someString", "some string");
SomeEntity saved = list.get(0);
saved.setSomeString("something else");
// this will trigger just an update on someString
bin.putIn(saved);
Removing can be just as simple as saving.
BeanBin<SomeEntity> bin = new BeanBin<SomeEntity>(SomeEntity.class);
List<SomeEntity> list = bin.matches("someString", "some string");
SomeEntity saved = list.get(0);
// deletes the saved entity
bin.takeOut(saved);
You can manage how your data gets saved and removed easily with a BeanBin transaction. Check out the example below.
BeanBin<SomeEntity> bin = new BeanBin<SomeEntity>(SomeEntity.class);
List<SomeEntity> results = bin.contains("property", "term");
SomeEntity entity1 = results.get(0);
SomeEntity entity2 = results.get(1);
SomeEntity entity3 = results.get(2);
entity1.setProperty("something");
entity2.setProperty("something else");
Transaction transaction = bin.getTransaction();
transaction.begin();
bin.putIn(entity1);
bin.putIn(entity2);
bin.takeOut(entity3);
transaction.commit();
// everything was commited within the same transaction
Inside the BeanBin jar file there is a junit.framework.TestCase subclass that will make testing with BeanBin possible. You have to have a JBoss running with BeanBin and have your entities deployed somewhere along the JBoss classpath. Also, you have to have all of the libraries in the testlib/ directory within the latest release zip file in the test's classpath.
BeanBinTestCase creates its own InitialContext that connects to a remote JBoss server's JNDI. Just extend BeanBinTestCase and supply the super constructor with the server name and JNDI port number.
public class SomeTest extends BeanBinTest {
public SomeTest() {
super("localhost", 1099);
}
public void testBasic() throws Exception {
// test something that uses BeanBin directly or indirectly...
}
}
BeanBin will use the JBoss server's BeanBin session bean to save, delete and search a live DataSource within your unit tests.
Currently, clustering JBoss application servers will work with BeanBin but under certain conditions. You must only have the BeanBin jar file in one of the server's deploy directory. All of the other servers must have the BeanBin jar file inside their lib folder. It will also only be available to those services running within the DefaultPartition.
There is much room for improvement here including establishing a cluster of BeanBin session beans and a user defined partition name but these are not available at the time of writing.
BeanBin started out as a proof of concept and is now a very alpha release. The following are a few of the future enhancements I believe BeanBin should have: