Jarosław Kijanowski
Drools meets Hibernate
Abstract:
Drools evaluates facts which are present in the working memory. But could it also reason over data stored in a relational database? This feature would extend Drools' range of applicability and since this is an often asked question in the mailing list, it's worth to know the answer which sounds: "of course Drools can!".
Hibernate, one of the most favorite ORM tools, allows to handle data stored in a relational database. This article will describe how one can access a Hibernate session from inside the rule engine. I will use PostgreSQL as a data source. Besides that I will create two classes, Game and Player, having a many-to-many relationship.
- Creating a new Drools project
You still don't have installed the Drools 4.0.7 Eclipse Workbench? Just have a look at this article: "Rules fall from your eyes".
- Class files
- Hibernate
-
hibernate-3.2.6.GA/lib/antlr-2.7.6.jar
-
hibernate-3.2.6.GA/lib/asm.jar
-
hibernate-3.2.6.GA/lib/cglib-2.1.3.jar
-
hibernate-3.2.6.GA/lib/commons-collections-2.1.1.jar
-
hibernate-3.2.6.GA/lib/commons-logging-1.0.4.jar
-
hibernate-3.2.6.GA/lib/dom4j-1.6.1.jar
-
hibernate-3.2.6.GA/hibernate3.jar
-
hibernate-3.2.6.GA/lib/jta.jar
- Drools
- How does it work?
-
return results back to the application
-
access services, like Hibernate, JMS, file writers, email senders, etc.
- Results
- Summary
Menu File -> New -> Project -> Drools - > Rule Project -> Next. Provide the project's name, drools-hibernate-demo, and click on Next, to uncheck the example files
Create a package "eu.kijanowski.drools.hibernate" in the src/main/java directory. We will use two class files, Player.java and Game.java. They have a many-tom-many relationship, since one player can own many games and one game can be owned by many players ;)
Class Game.java
package eu.kijanowski.drools.hibernate;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
public class Game implements Serializable {
private static final long serialVersionUID = 1L;
private Long id = null;
private String name;
private double price;
private int levels;
private Set<Player> players = new HashSet<Player>();
public Game() {
}
public Game(String name, double price, int levels) {
this.name = name;
this.price = price;
this.levels = levels;
}
public Long getId() {
return id;
}
@SuppressWarnings("unused")
private void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getLevels() {
return levels;
}
public void setLevels(int levels) {
this.levels = levels;
}
public Set<Player> getPlayers() {
return players;
}
public void setPlayers(Set<Player> players) {
this.players = players;
}
public String toString() {
return name + " with " + levels + " levels for " + price;
}
}
Class Player.java
package eu.kijanowski.drools.hibernate;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
public class Player implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private int age;
private Set<Game> games = new HashSet<Game>();
public Player() {
}
public Player(String name, int age, HashSet<Game> games) {
this.name = name;
this.age = age;
this.games = games;
}
public Long getId() {
return id;
}
@SuppressWarnings("unused")
private void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Set<Game> getGames() {
return games;
}
public void setGames(Set<Game> games) {
this.games = games;
}
public String toString() {
return name;
}
}
Without going into details, download the binaries, Hibernate Core 3.2.6 and extract the archive.
In our project create a directory called lib and copy over following files:
Now we have to add these libraries to the "build path": from the menu choose Project -> Properties -> Java Build Path -> Libraries and then Add External JARs and navigate to the project's lib directory. Mark all files and click OK.
It's time to configure Hibernate. In the src/main/java directory create a file called hibernate.cfg.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">org.postgresql.Driver</property>
<property name="connection.url">jdbc:postgresql://localhost/droolsdb</property>
<property name="connection.username">droolsuser</property>
<property name="connection.password"></property>
<property name="connection.pool_size">1</property>
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="current_session_context_class">thread</property>
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">create</property>
<mapping resource="eu/kijanowski/drools/hibernate/model.hbm.xml"/>
</session-factory>
</hibernate-configuration>
As you see, we're going to use the org.postgresql.Driver class, hence we need to add another library to our project. From the PostgreSQL home page we can download the required driver. I'm using 8.2-508 JDBC 3. Don't forget to add it to the Java Build Path, like we did with the hibernate library.
It's time to download and start PostgreSQL and create a user and schema.
/usr/local/pgsql/bin/postgres -D /usr/local/pgsql/data
>logfile 2>&1 &
/usr/local/pgsql/bin/createuser -h 127.0.0.1 -p 5432 -U postgres -s -d
-R droolsuser
/usr/local/pgsql/bin/createdb -h 127.0.0.1 -p 5432 -U postgres -O
droolsuser droolsdb
The last step is mapping - this is connecting the world of Java objects with the world of relations (tabels). Create model.hbm.xml in the eu.kijanowski.drools.hibernate package:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eu.kijanowski.drools.hibernate">
<class name="Player" table="PLAYERS">
<id name="id" column="PLAYER_ID" type="java.lang.Long">
<generator class="sequence">
<param name="sequence">player_id_seq</param>
</generator>
</id>
<property name="name" column="NAME" length="10" not-null="true"
type="java.lang.String" />
<property name="age" column="AGE" not-null="true"
type="java.lang.Integer" />
<set name="games" table="PLAYERS_GAMES" cascade="persist, delete, merge, save-update">
<key column="PLAYER_ID" />
<many-to-many column="GAME_ID" class="Game" />
</set>
</class>
<class name="Game" table="GAMES">
<id name="id" column="GAME_ID" type="java.lang.Long">
<generator class="sequence">
<param name="sequence">game_id_seq</param>
</generator>
</id>
<property name="name" column="NAME" length="10" not-null="true"
type="java.lang.String" />
<property name="price" column="PRICE" not-null="true"
type="java.lang.Double" />
<property name="levels" column="LEVELS" not-null="true"
type="java.lang.Integer" />
<set name="players" table="PLAYERS_GAMES">
<key column="GAME_ID" />
<many-to-many column="PLAYER_ID" class="Player"/>
</set>
</class>
</hibernate-mapping>
We're almost there. We olny have to create the rule file and a testing class which will start our example. But how are we going to use Hibernate in our application? Well, in our testing class we will create a Hibernate session and provide it to the rule engine's working memory as a constant global value. This allows us to access the database from inside the rule file. Next we will create a query and access the result's objects using the keyword "from". Let's do this:
Class DroolsHibernateTest.java
package eu.kijanowski.drools.hibernate;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.WorkingMemory;
import org.drools.compiler.PackageBuilder;
import org.drools.rule.Package;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class DroolsHibernateTest {
private final static SessionFactory factory;
static {
Configuration cfg = new Configuration().configure();
factory = cfg.buildSessionFactory();
}
public static final void main(String[] args) {
try {
Game g1 = new Game("BF2", 39.99, 5);
Game g2 = new Game("AoE3", 129.99, 45);
Game g3 = new Game("BF3", 139.99, 15);
HashSet<Game> games1 = new HashSet<Game>();
games1.add(g1);
games1.add(g2);
games1.add(g3);
HashSet<Game> games2 = new HashSet<Game>();
games2.add(g1);
games2.add(g3);
Player p1 = new Player("jarek", 26, games1);
Player p2 = new Player("ewelina", 25, games2);
System.out.println(p1.getGames());
System.out.println(p2.getGames());
/* open a Hibernate session and persist data */
Session session = factory.openSession();
Transaction tx = session.beginTransaction();
session.save(p1);
session.save(p2);
tx.commit();
session.close();
/* Let's verify the persisted data */
session = factory.openSession();
tx = session.beginTransaction();
List<Player> players = session.createCriteria(Player.class).list();
System.out.println(players.size() + " player(s) found:");
for (Iterator<Player> iter = players.iterator(); iter.hasNext();) {
Player player = (Player) iter.next();
System.out.println(player.getName() + " has following games: "
+ player.getGames());
}
tx.commit();
session.close();
// load up the rulebase
RuleBase ruleBase = readRule();
WorkingMemory workingMemory = ruleBase.newStatefulSession();
/* pass a hibernate session to the working memory as a global */
session = factory.openSession();
workingMemory.setGlobal("hibernateSession", session);
workingMemory.fireAllRules();
session.close();
} catch (Throwable t) {
t.printStackTrace();
}
}
private static RuleBase readRule() throws Exception {
// read in the source
Reader source = new InputStreamReader(DroolsHibernateTest.class
.getResourceAsStream("/Demo.drl"));
PackageBuilder builder = new PackageBuilder();
// this will parse and compile in one step
builder.addPackageFromDrl(source);
// get the compiled package (which is serializable)
Package pkg = builder.getPackage();
// add the package to a rulebase (deploy the rule package).
RuleBase ruleBase = RuleBaseFactory.newRuleBase();
ruleBase.addPackage(pkg);
return ruleBase;
}
}
The rule file can be created using the drools plug-in. Just click on the src/main/rules directory and from the menu File -> New -> Other... click Drools -> Rule Resource:
Demo.drl
package eu.kijanowski.drools.hibernate
global org.hibernate.Session hibernateSession;
rule "hibernate_from"
when
game:Game() from hibernateSession.createQuery("select games from Player p where p.age >= :age").setProperties( {"age" : 18 }).list()
then
System.out.println("The game "+game.getName() +"is owned by "+game.getPlayers());
end
Let's have a look at the global keyword in the rule file. This allows us to provide the rule engine with constants and variables from outside. Potential usecases are:
What's worth to know is that you should never change a global, when you use it in the condition part (LHS) of a rule. Moreover globals are not designed to share any data between rules, this can be done via facts living in the working memory.
The condition element from allows to access data from other sources than the working memory. These can be Collectins, Maps and results coming from called methods. In our scenario we've created a query and received a list of games.
After running our example (Run as -> Java Application) the console will show:
Jun 14, 2008 8:58:11 AM org.hibernate.cfg.Environment <clinit>
INFO: Hibernate 3.2.6
Jun 14, 2008 8:58:11 AM org.hibernate.cfg.Environment <clinit>
INFO: hibernate.properties not found
Jun 14, 2008 8:58:11 AM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: Bytecode provider name : cglib
Jun 14, 2008 8:58:11 AM org.hibernate.cfg.Environment <clinit>
INFO: using JDK 1.4 java.sql.Timestamp handling
Jun 14, 2008 8:58:11 AM org.hibernate.cfg.Configuration configure
INFO: configuring from resource: /hibernate.cfg.xml
Jun 14, 2008 8:58:11 AM org.hibernate.cfg.Configuration getConfigurationInputStream
INFO: Configuration resource: /hibernate.cfg.xml
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.Configuration addResource
INFO: Reading mappings from resource : eu/kijanowski/drools/hibernate/model.hbm.xml
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.HbmBinder bindRootPersistentClassCommonValues
INFO: Mapping class: eu.kijanowski.drools.hibernate.Player -> PLAYERS
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.HbmBinder bindCollection
INFO: Mapping collection: eu.kijanowski.drools.hibernate.Player.games -> PLAYERS_GAMES
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.HbmBinder bindRootPersistentClassCommonValues
INFO: Mapping class: eu.kijanowski.drools.hibernate.Game -> GAMES
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.HbmBinder bindCollection
INFO: Mapping collection: eu.kijanowski.drools.hibernate.Game.players -> PLAYERS_GAMES
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.Configuration doConfigure
INFO: Configured SessionFactory: null
Jun 14, 2008 8:58:12 AM org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: Using Hibernate built-in connection pool (not for production use!)
Jun 14, 2008 8:58:12 AM org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: Hibernate connection pool size: 1
Jun 14, 2008 8:58:12 AM org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: autocommit mode: false
Jun 14, 2008 8:58:12 AM org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: using driver: org.postgresql.Driver at URL: jdbc:postgresql://localhost/droolsdb
Jun 14, 2008 8:58:12 AM org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: connection properties: {user=droolsuser, password=****}
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: RDBMS: PostgreSQL, version: 8.2.5
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC driver: PostgreSQL Native Driver, version: PostgreSQL 8.2 JDBC3 with SSL (build 508)
Jun 14, 2008 8:58:12 AM org.hibernate.dialect.Dialect <init>
INFO: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Jun 14, 2008 8:58:12 AM org.hibernate.transaction.TransactionFactoryFactory buildTransactionFactory
INFO: Transaction strategy: org.hibernate.transaction.JDBCTransactionFactory
Jun 14, 2008 8:58:12 AM org.hibernate.transaction.TransactionManagerLookupFactory getTransactionManagerLookup
INFO: No TransactionManagerLookup configured (in JTA environment, use of read-write or transactional second-level cache is not recommended)
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Automatic flush during beforeCompletion(): disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Automatic session close at end of transaction: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC batch size: 15
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC batch updates for versioned data: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Scrollable result sets: enabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC3 getGeneratedKeys(): disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Connection release mode: auto
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Default batch fetch size: 1
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Generate SQL with comments: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Order SQL updates by primary key: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Order SQL inserts for batching: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory createQueryTranslatorFactory
INFO: Query translator: org.hibernate.hql.ast.ASTQueryTranslatorFactory
Jun 14, 2008 8:58:12 AM org.hibernate.hql.ast.ASTQueryTranslatorFactory <init>
INFO: Using ASTQueryTranslatorFactory
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Query language substitutions: {}
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JPA-QL strict compliance: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Second-level cache: enabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Query cache: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory createCacheProvider
INFO: Cache provider: org.hibernate.cache.NoCacheProvider
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Optimize cache for minimal puts: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Structured second-level cache entries: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Echoing all SQL to stdout
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Statistics: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Deleted entity synthetic identifier rollback: disabled
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Default entity-mode: pojo
Jun 14, 2008 8:58:12 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Named query checking : enabled
Jun 14, 2008 8:58:12 AM org.hibernate.impl.SessionFactoryImpl <init>
INFO: building session factory
Jun 14, 2008 8:58:12 AM org.hibernate.impl.SessionFactoryObjectFactory addInstance
INFO: Not binding factory to JNDI, no JNDI name configured
Jun 14, 2008 8:58:12 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: Running hbm2ddl schema export
Jun 14, 2008 8:58:12 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: exporting generated schema to database
Jun 14, 2008 8:58:13 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: schema export complete
[BF3 with 15 levels for 139.99, BF2 with 5 levels for 39.99, AoE3 with 45 levels for 129.99]
[BF3 with 15 levels for 139.99, BF2 with 5 levels for 39.99]
Hibernate: select nextval ('player_id_seq')
Hibernate: select nextval ('game_id_seq')
Hibernate: select nextval ('game_id_seq')
Hibernate: select nextval ('game_id_seq')
Hibernate: select nextval ('player_id_seq')
Hibernate: insert into PLAYERS (NAME, AGE, PLAYER_ID) values (?, ?, ?)
Hibernate: insert into GAMES (NAME, PRICE, LEVELS, GAME_ID) values (?, ?, ?, ?)
Hibernate: insert into GAMES (NAME, PRICE, LEVELS, GAME_ID) values (?, ?, ?, ?)
Hibernate: insert into GAMES (NAME, PRICE, LEVELS, GAME_ID) values (?, ?, ?, ?)
Hibernate: insert into PLAYERS (NAME, AGE, PLAYER_ID) values (?, ?, ?)
Hibernate: insert into PLAYERS_GAMES (PLAYER_ID, GAME_ID) values (?, ?)
Hibernate: insert into PLAYERS_GAMES (PLAYER_ID, GAME_ID) values (?, ?)
Hibernate: insert into PLAYERS_GAMES (PLAYER_ID, GAME_ID) values (?, ?)
Hibernate: insert into PLAYERS_GAMES (PLAYER_ID, GAME_ID) values (?, ?)
Hibernate: insert into PLAYERS_GAMES (PLAYER_ID, GAME_ID) values (?, ?)
Hibernate: select this_.PLAYER_ID as PLAYER1_0_0_, this_.NAME as NAME0_0_, this_.AGE as AGE0_0_ from PLAYERS this_
2 player(s) found:
Hibernate: select games0_.PLAYER_ID as PLAYER1_1_, games0_.GAME_ID as GAME2_1_, game1_.GAME_ID as GAME1_2_0_, game1_.NAME as NAME2_0_, game1_.PRICE as PRICE2_0_, game1_.LEVELS as LEVELS2_0_ from PLAYERS_GAMES games0_ left outer join GAMES game1_ on games0_.GAME_ID=game1_.GAME_ID where games0_.PLAYER_ID=?
jarek has following games: [AoE3 with 45 levels for 129.99, BF3 with 15 levels for 139.99, BF2 with 5 levels for 39.99]
Hibernate: select games0_.PLAYER_ID as PLAYER1_1_, games0_.GAME_ID as GAME2_1_, game1_.GAME_ID as GAME1_2_0_, game1_.NAME as NAME2_0_, game1_.PRICE as PRICE2_0_, game1_.LEVELS as LEVELS2_0_ from PLAYERS_GAMES games0_ left outer join GAMES game1_ on games0_.GAME_ID=game1_.GAME_ID where games0_.PLAYER_ID=?
ewelina has following games: [BF3 with 15 levels for 139.99, BF2 with 5 levels for 39.99]
Hibernate: select game2_.GAME_ID as GAME1_2_, game2_.NAME as NAME2_, game2_.PRICE as PRICE2_, game2_.LEVELS as LEVELS2_ from PLAYERS player0_ inner join PLAYERS_GAMES games1_ on player0_.PLAYER_ID=games1_.PLAYER_ID inner join GAMES game2_ on games1_.GAME_ID=game2_.GAME_ID where player0_.AGE>=?
Hibernate: select players0_.GAME_ID as GAME2_1_, players0_.PLAYER_ID as PLAYER1_1_, player1_.PLAYER_ID as PLAYER1_0_0_, player1_.NAME as NAME0_0_, player1_.AGE as AGE0_0_ from PLAYERS_GAMES players0_ left outer join PLAYERS player1_ on players0_.PLAYER_ID=player1_.PLAYER_ID where players0_.GAME_ID=?
The game BF2is owned by [jarek, ewelina]
Hibernate: select players0_.GAME_ID as GAME2_1_, players0_.PLAYER_ID as PLAYER1_1_, player1_.PLAYER_ID as PLAYER1_0_0_, player1_.NAME as NAME0_0_, player1_.AGE as AGE0_0_ from PLAYERS_GAMES players0_ left outer join PLAYERS player1_ on players0_.PLAYER_ID=player1_.PLAYER_ID where players0_.GAME_ID=?
The game BF3is owned by [jarek, ewelina]
Hibernate: select players0_.GAME_ID as GAME2_1_, players0_.PLAYER_ID as PLAYER1_1_, player1_.PLAYER_ID as PLAYER1_0_0_, player1_.NAME as NAME0_0_, player1_.AGE as AGE0_0_ from PLAYERS_GAMES players0_ left outer join PLAYERS player1_ on players0_.PLAYER_ID=player1_.PLAYER_ID where players0_.GAME_ID=?
The game AoE3is owned by [jarek]
The game BF2is owned by [jarek, ewelina]
The game BF3is owned by [jarek, ewelina]
This article shows how you can easily integrate Hibernate with the Drools rule engine. This allows you to reason over data, which does not only live in the working memory, but also in an external database.
Feel free to leave a comment.