We will need a class that will store out instances. We’re going to create methods for inserting, reading, updating and deleting data.
To make changes to a database, we must use transcations. A transcation is a set of changes to be done to the database. In this example, the transcation implementation is a thin and simple wrapper over Hibernate’s Transcation
class. It is not necessarily needed to start a transcation if all we intend to do is to query the database and not modify any data.
WARN: We are not alloed to modify a managed entity outside a transaction!
Hibernate keeps most of the data inside the database, but it also keeps a pool of cached objects in memory. By default, it does not clear this cache automatically, so it can lead to memory leaks. To combat, we will create a method clearCachedEntitied
to be called by the application code after an operation ends. The entities are cleared when a transcation ends (or more accurate, we will make it do so), but query operations alone can cause memory leaks if we don’t clear the cache manually.
H2 comes with a built-in database explorer as a web interface that runs in a browser. We can turn on or off this feature with the showExplorer
parameter.
We will need to keep a reference to the Hibernate session, against which we will execute the operations. A reference to a Transcation object is also kept. This field is null
when there is no transcation active.
public class ChatData {
/* data */
private final Session session;
private Transaction transaction;
public ChatData(String dbPath, boolean showExplorer);
/* save new entities */
public void saveEntity(Object obj);
/* find any entities */
public <T> Stream<T> findAll(Class<T> clazz);
public <T> Optional<T> findById(Class<T> clazz, String id);
/* find specific entities */
public Stream<User> findUsersByName(String name);
public Stream<Message> findMessagesWithWord(String word);
public Optional<Emote> findEmoteByRepresentation(String repr);
/* delete entities */
public <T> Optional<T> deleteEntiy(Class<T> clazz, String id);
/* transcations */
public void openTransaction();
public void closeTransaction();
/* clear cached entities */
public void clearCachedEntities();
/* shutdown the database */
public void close();
}
Connecting and disconnecting to the database
We will create a Session object, given a path. Here we have the possibility to open a H2 Explorer window. Keep in mind that this needs to be done separately, in another thread.
When disconnecting, ensure that a possibly ongoing transcation is commited to the database.
public ChatData(String dbPath, boolean openExplorer) {
Configuration cfg = new Configuration()
.addAnnotatedClass(User.class)
.addAnnotatedClass(Message.class)
.addAnnotatedClass(Emote.class);
ServiceRegistry sevReg = new StandardServiceRegistryBuilder()
.applySettings(hibernatePropsH2("jdbc:h2:" + dbPath))
.build();
SessionFactory sessionFactory = cfg.buildSessionFactory(sevReg);
session = sessionFactory.openSession();
if (openExplorer) {
new Thread(() -> {
try {
org.h2.tools.Server.startWebServer(sevReg.getService(ConnectionProvider.class).getConnection());
} catch (SQLException exception) {
exception.printStackTrace();
}
}).start();
}
}
private static Properties hibernatePropsH2(String h2url) {
Properties hProps = new Properties();
hProps.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
hProps.setProperty("hibernate.connection.driver_class", "org.h2.Driver");
hProps.setProperty("hibernate.connection.url", h2url);
hProps.setProperty("hibernate.connection.username", "sa");
hProps.setProperty("hibernate.connection.password", "");
hProps.setProperty("hibernate.hbm2ddl.auto", "update");
hProps.setProperty("hibernate.show_sql", "false");
return hProps;
}
public void close() {
closeTransaction();
session.close();
}
Transcation management
Only create a new transcation if none is active, and only commit if there is a transaction.
public void openTransaction() {
if (transaction == null) {
transaction = session.beginTransaction();
}
}
public void closeTransaction() {
if (transaction != null) {
transaction.commit();
transaction = null;
}
clearCachedEntities();
}
public void clearCachedEntities() {
session.clear();
}
Saving and deleting entities
When deleting, we search the database for a given ID and delete the entry.
|
The application code is responsible for severing relationships with other entities before a delete operation, otherwise all linked objects will be deleted too!
|
public void saveEntity(Object obj) {
if (obj instanceof Emote || obj instanceof Message || obj instanceof User) {
autoTransaction(() -> session.save(obj));
}
}
public <T> Optional<T> deleteEntiy(Class<T> clazz, String id) {
T ent = session.find(clazz, id);
if (ent != null) {
autoTransaction(() -> session.delete(ent));
return Optional.of(ent);
} else return Optional.empty();
}
private void autoTransaction(Runnable action) {
if (transaction == null) {
openTransaction();
action.run();
closeTransaction();
} else action.run();
}
Finding entities
We will create two methods for accessing entities: by Id, and bulk acess.
public <T> Optional<T> findById(Class<T> clazz, String id) {
return Optional.ofNullable(session.find(clazz, id));
}
public <T> Stream<T> findAll(Class<T> clazz) {
CriteriaQuery<T> cq = session.getCriteriaBuilder().createQuery(clazz);
return session.createQuery(cq.select(cq.from(clazz))).getResultStream();
}
Finding entities with query
Sometimes it’s needed to find entities that respect a certain critaria that is specified by it’s fields. We could use findAll
and iterate over the result, but it would be need to fetch the entire database into memory, at once. Instead, we’ll use an JPQL query.
A query object is first created with parameters (?1
), then the parameter is set and the query is executed.
public Stream<User> findUsersByName(String name) {
TypedQuery<User> query = session.createQuery("SELECT user FROM User AS user WHERE user.name = ?1");
query.setParameter(1, name);
return query.getResultStream();
}
public Stream<Message> findMessagesWithWord(String word) {
TypedQuery<Message> query = session.createQuery("SELECT mess FROM Message AS mess WHERE mess.content LIKE ?1");
query.setParameter(1, word);
return query.getResultStream();
}
public Optional<Emote> findEmotebyRepresentation(String repr) {
TypedQuery<Emote> query = session.createQuery("SELECT em FROM Emote AS em WHERE em.representation = ?1");
query.setParameter(1, repr);
return Optional.ofNullable(query.getSingleResult());
}