Mit etwas Verspätung kommt nun der 2. Teil des FRF indem ich mich mit Morphia und MongoDB auseinander gesetzt habe. Die Einarbeitung in beide Themen hat doch mehr Zeit in Anspruch genommen als vermutet. Zudem hatte ich zu Beginn ein Paar Probleme, bis mein Maven und die MongoDB liefen (OSX spezifischer Natur).
Ziel
Das Ergebnis dieses Parts wird es sein, dass CRUD Prinzip mit Hilfe von Morphia in der MongoDB abzubilden. Dazu verwenden wir DAOs und POJOs, welche mit Morphia Annotationen versehen werden.
Das Wichtigste vorweg, MongoDB sollte gestartet sein. Dazu einfach den MongoDB Daemon starten (mongod).
Vorraussetzungen
Maven2 oder Maven3 installiert und konfiguriert. Des Weiteren sollte das m2eclipse Plugin installiert sein. Man sollte wissen wie man Maven einsetzt. Ich werde auf bestimmte Aspekte im Zusammenhang mit diesem Tutorial eingehen, aber es wird keine Einführung in Maven sein.
Ready, Steady, Go
Create & Configuring our Maven Project (Ready)
Zu Beginn sollte ein neues Maven Projekt erstellt werden. Als Archtype sollte maven-archtype-sample gewählt werden. Danach nur noch bei Artifact Id den Namen des Projektes eintragen und Projekt erstellen. Unter Umständen kann es sein, dass es als Java 1.5 Projekt angelegt wird, falls dies so sein sollte, einfach folgenden Absatz in die POM kopieren
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build>
und dann Rechtsklick auf das Projekt -> Maven -> Update Project Configuration… Nun sollten etwaige Fehlermeldungen verschwinden. Wenn wir uns schon in der POM aufhalten, dann passen wir gleich die JUnit Version an und zwar nehmen wir die Aktuellste – 4.10. Ansonsten benötigen wir nur noch die Dependencies für Morphia und den MongoDB Java Driver.
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> <dependency> <groupId>com.google.code.morphia</groupId> <artifactId>morphia</artifactId> <version>0.99</version> </dependency> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongo-java-driver</artifactId> <version>2.6.5</version> </dependency> <dependency> <groupId>com.google.code.morphia</groupId> <artifactId>morphia-validation</artifactId> <version>0.99</version> </dependency> </dependencies>
Damit Maven die Morphia Dependencies findet muss noch das Morphia Repository hinzugefügt werden.
<repositories> <repository> <id>morphia</id> <url>http://morphia.googlecode.com/svn/mavenrepo/</url> </repository> </repositories>
POJOs & DAOs (Steady)
Da das grundlegende Maven-Setup nun steht kann es endlich losgehen. Wir benötigen 2 POJOs, POJO A referenziert POJO B, eine Singelton für den DB Zugriff, entsprechende DAOs.
The Singelton
Das Singelton-Pattern sollte jedem geläufig sein. Es bietet sich gerade dazu an für den Zugriff auf die MongoDB. Hierfür erstellen wir eine neue Klasse mit einem privaten Konstruktor in dem der Verbindungsaufbau zur DB abläuft. Ich nenne die Klasse MongoConnectionManager.java.
public class MongoConnectionManager {
private static final MongoConnectionManager INSTANCE = new MongoConnectionManager();
private final Datastore db;
public static final String DB_NAME = "personDB";
private MongoConnectionManager() {
try {
Mongo m = new Mongo("localhost", 27017);
db = new Morphia().map(Person.class).map(Company.class).createDatastore(m, DB_NAME);
db.ensureIndexes();
} catch (Exception e) {
throw new RuntimeException("Error initializing mongo db", e);
}
}
public static MongoConnectionManager getInstance() {
return INSTANCE;
}
public Datastore getDb() {
return db;
}
}
Was passiert hier? Wir stellen eine Verbindung auf die MongoDB mit Hilfe des MongoDB Java Drivers her. Als Parameter wird der Host und der entsprechende Port mitgeben.
Mongo m = new Mongo("localhost", 27017);
Die folgende Zeilen sind Morphia Spezifisch. Es werden unsere Entities (POJOs) registriert und die Tabelle (hier Datastore) in unserer Datenbank erstellt. Die Methode ensureIndexes(); erstellt synchron die Indexes und die gekapselten Collections für unsere Entities.
db = new Morphia().map(Person.class).map(Company.class).createDatastore(m, DB_NAME); db.ensureIndexes();
The POJOs
In diesem Beispiel verwende ich 2 POJO Klassen, Person und Company. Jeder Person ist eine Company zugeordnet.
@Entity(value="persons", noClassnameStored=true)
public class Person {
@Id
private ObjectId id;
private String name;
private String surename;
private String Email;
@Reference
private Company company;
//Gettter/Setter
...
- @Entity wird benötigt um mitzuteilen, dass diese Klasse als Entität verwendet werden soll. Morphia persistiert diese Klasse dann dementsprechend.
- @Id wird im Zusammenhang mit der Klasse ObjectId verwendet. @Id sagt aus, dass dieses Attribut vom Typ ObjectId, bei fehlerfreien persistieren die entsprechende Id von der MongoDB zugewiesen bekommt.
- @Reference stellt die Verbindung/Verknüpfung mit der Klasse Company dar.
Die wichtigsten Annotationen sind @Entity und @Id, die vorhanden sein müssen.
@Entity(value="companies", noClassnameStored=true)
public class Company {
@Id
private ObjectId id;
private String name;
private Long noEmployees;
//Getter/Setter
...
Die Klasse Company benötigt keine weiteren Morphia Annotationen außer @Enity und @Id.
The DAOs
Data Access Objekt was ist das? Hier lesen!
Use a Data Access Object (DAO) to abstract and encapsulate all access to the data source. The DAO manages the connection with the data source to obtain and store data.
Ich baue meine DAOs immer wie folgt auf, einmal das Interface EntityDAO und die Implementierung dazu EntityDAOImpl. In das Interface kommen die gewünschten Methoden zum Suchen, Erstellen, Löschen, … von Datensätzen.
public interface PersonDAO {
public Key<Person> save(Person person);
public Person findPerson(ObjectId personId);
public List<Person> findAllPersons();
public List<Person> findPersonsByCompany(Company company);
}
Nun erben wir u.a. von unseren oben erstellten Interface und programmieren unsere Methoden aus.
public class PersonDAOImpl extends BasicDAO<Person, ObjectId> implements PersonDAO {
public PersonDAOImpl(){
super(Person.class, MongoConnectionManager.getInstance().getDb());
}
@Override
public Person findPerson(ObjectId personId) {
return get(personId);
}
@Override
public List<Person> findAllPersons() {
return find().asList();
}
@Override
public List<Person> findPersonsByCompany(Company company) {
return createQuery().field("company").equal(company).asList();
}
}
Das war schon die ganze Zauberei. Nun sind wir soweit um unsere Anwendung zu testen.
Creating & Running JUnit Tests (Go)
Wir verwenden JUnit Tests um unsere Anwendung auf Funktionalität zu testen. Ist besser, sauberer und vor allem professioneller als eine Main Klasse zuschreiben und mit System.out.println(); zu arbeiten.
Dafür legen wir im Ordner src/test/java unterhalb des Packages eine neue JUnit Klasse an. Ich hab meine PersonDAOTest.java genannt.
Ich habe 2 Tests geschrieben, einen der prüft ob ein Datensatz persistiert wurde, der Andere versucht einen Datensatz zu finden.
Wichtig ist, dass die Companies bereits existieren (persistiert sind) wenn sie den Personen zugeordnet werden. Falls dies nicht der Fall ist, bricht der Test mit einer MappingException ab! Siehe Kommentar im Quellcode.
public class PersonDAOTest {
private PersonDAO personDAO;
private BasicDAO<Company, ObjectId> companyDAO;
private final Company ibm = new Company().withName("IBM").withNumberOfEmployees(Long.valueOf(426751));
private final Company samsung = new Company().withName("Samsung").withNumberOfEmployees(Long.valueOf(290000));
@Before
public void before() {
Util.drop();
personDAO = new PersonDAOImpl();
companyDAO = new BasicDAO<Company, ObjectId>(Company.class, MongoConnectionManager.getInstance().getDb());
/**
* "Pre" store the company, so that they will haven an objectId, otherwise it will throw new Mapping Exception
* see here for more information: http://code.google.com/p/morphia/issues/detail?id=315
*/
ArrayList<Company> arrList = new ArrayList<Company>();
arrList.add(ibm);
arrList.add(samsung);
for (Company c : arrList) {
companyDAO.save(c);
}
}
private Person createPerson(){
Person person = new Person();
person.setSurename("Sue");
person.setName("Walker");
person.setEmail("sue.walker@ibm.com");
person.withCompany(ibm);
personDAO.save(person);
return person;
}
@Test
public void testPersistance() {
Person person = createPerson();
assertNotNull("Saved Person Id", person.getId());
person = personDAO.findPerson(person.getId());
assertNotNull("Person retrieved", person);
}
@Test
public void query() {
createPerson();
List<Person> persons = personDAO.findPersonsByCompany(ibm);
assertTrue("Returned one Person" , persons.size() == 1);
persons = personDAO.findPersonsByCompany(samsung);
assertTrue("Nothing should returned" , persons.size() == 0);
}
}
Sobald die JUnit Klasse fertig gestellt wurde kann man mit
mvn test
die Tests ausführen.
Ich denke die Klasse ist recht selbsterklärend, falls doch Erklärungsbedarf bestehen sollte, schreibts in die Kommentare ich werde es mit in den Artikel einfließen lassen.
Mit einem Administrationstool wie z.B. MongoHub (OSX) kann mit Hilfe einer grafischen Oberfläche nachgeschaut werden ob etwas persistiert wurde.
Comming Next …
Im nächsten Part versuche ich dem ganzen eine Oberfläche zu verpassen. Das Framework wird Vaadin sein. Es soll dann möglich sein neue Einträge hinzuzufügen, zu modifizieren und zu löschen mit Hilfe einer UI. Dort kommt dann der Tomcat noch mit ins Spiel.
Last But Not Least …
… habe ich noch ein paar Interessante Ressourcen zum Thema MongoDB und Morphia gefunden. Diese Links dienten als Quellen und Anhaltspunkte für diesen Artikel:
http://www.mongodb.org/display/DOCS/Quickstart
http://www.mongodb.org/display/DOCS/Admin+UIs
http://www.mongodb.org/display/DOCS/SQL+to+Mongo+Mapping+Chart
http://code.google.com/p/morphia/wiki/QuickStart
http://sleeplessinslc.blogspot.com/2010/10/mongodb-with-morphia-example.html
http://mongohub.todayclose.com/download
Das Projekt kann gezippt heruntergeladen werden. GitHub folgt!
Version 0.2