bukkit's database engine
DESCRIPTION
A short HowTo about how to store data in databases taking advantage of bukkit's persistence mechanism. Bukkit is a modularised server implementation for Minecraft. This document is intended for bukkit plugin developers that are not too familiar with JPA. It also contains some best-practise remarks about common plugin development from my point of view.TRANSCRIPT
Bukkit's Database EngineAnd some remarks regarding plugin developmentby Urs P. Stettler
Image Source
Bukkit's Way
Bukkit provides a database abstraction for the two databases it knows
● SQLite (www.sqlite.org)● MySQL (www.mysql.com)
using the ORM library
● Ajave (www.ajave.org)
HowTo
As a plugin developer you have to do four additional things before you can start using a database● Create a simple maven project● Configure your plugin to use a database● Create a main class for the plugin● Create model classes (beans, pojos) to represent the
data● Register the beans with your plugin● Use the methods provided by bukkit to access the
database
The blue steps are necessary for any plugin
Create a new maven project
Your plugin's main class
Your default configuration
Your plugin description
Your maven Project Object Model
Bukkit API is available as a maven artifact.
Maven configuration (1/3)
Maven does the dependency management for you, and more.<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
<modelVersion>4.0.0</modelVersion>
<groupId>my.org</groupId><artifactId>test-plugin</artifactId><version>0.0.1-SNAPSHOT</version><name>TestPlugin</name>
<dependencies><dependency>
<groupId>org.bukkit</groupId><artifactId>bukkit</artifactId><version>1.4.5-R1.0</version>
</dependency></dependencies>
Import the bukkit API
Name your plugin
pom.xml
Maven configuration (2/3)pom.xml continued
<repositories><repository>
<id>bukkit</id><name>bukkit</name><url>http://repo.bukkit.org/content/repositories/releases/ </url>
</repository></repositories>
<properties><jdk>1.6</jdk><project.build.sourceEncoding> UTF-8</project.build.sourceEncoding>
<main.class>org.me.bukkit.TestPlugin </main.class></properties>
Tell maven how to get bukkit
Some settings about the project
Your plugin's main class
pom.xml
Maven configuration (3/3)pom.xml continued
<build><finalName>${project.name}</finalName><resources>
<resource><directory>src/main/resources </directory><filtering>true</filtering>
</resource></resources><plugins>
<plugin><groupId>org.apache.maven.plugins </groupId><artifactId>maven-compiler-plugin </artifactId><version>2.3.2</version><configuration>
<source>${jdk}</source><target>${jdk}</target><encoding>${project.build.sourceEncoding} </encoding>
</configuration></plugin>
</plugins></build>
</project>
Maven will replace place-holders in the config files
Tell maven how to build the project
pom.xml
plugin.yml (wiki)
This file tells bukkit how to handle your plugin.
# plugin config for bukkitname: ${project.name}main: ${main.class}version: ${project.version}description: > ${project.name} is a test plugin for bukkit.
database: true
commands: hello: description: Say hello.
Maven will replace these
This enables database support for the plugin
(Optionally) Register an in-game command /hello
config.yml
Did you know that you can define your plugin's default config here?
# default config for your pluginSomeKey: SomeValue
# where to store plugin data "FlatFile" or "Database"Storage: Database
It's always a good idea to also offer a flat-file alternative to store data
Plugin main classpackage org.me.bukkit;
public class TestPlugin extends JavaPlugin {
@Overridepublic List<Class<?>> getDatabaseClasses() {}
@Overridepublic boolean onCommand(final CommandSender sender,
final Command command, final String label, final String[] args) {}
@Overridepublic void onDisable() {}
@Overridepublic void onEnable() {}
}
On the next slides we go through each of these methods.
TestPlugin.java
onEnable() (1/3)
How to load the configuration and complete it with default values
@Overridepublic void onEnable() {
// read configFileConfiguration config = getConfig();// complete with default configconfig.options().copyDefaults( true);// load data from config// ...// save configsaveConfig();
// persistence
// listeners }
Load saved config from disk
Complete missing entries in config with the ones from the default config.yml
Save the now completed config to disk
You can always access the config with getConfig() . Bukkit keeps it in memory. No need to copy the values elsewhere.
TestPlugin.java
onEnable() (2/3)
Create and register listenersprivate DeathListener deathListener;
@Overridepublic void onEnable() {
// config; from previous slide
// persistence; next topic
// create listenersdeathListener = new DeathListener(this);// register registerPluginManager pm = getServer().getPluginManager();pm.registerEvents( deathListener, this);
}
TestPlugin.java
Event Listener example
The Event type of the method indicate what events the listeners expects
public class DeathListener implements Listener {
private final TestPlugin plugin;
public DeathListener(final TestPlugin plugin) {this.plugin = plugin;
}
@EventHandler(priority = EventPriority. NORMAL)public void onDeath(final EntityDeathEvent event) {
// do something useful}
}
This interfaces marks the class as a listener
The annotation marks the method as event listener
The method parameter indicates the event type
DeathListener.java
onEnable() (3/3)
Init the persistence implementation according to the plugin config
private IPersistence persistence;
@Overridepublic void onEnable() {
// config; from earlier
// create persistence instanceif ("FlatFile".equalsIgnoreCase(config.getString( "Storage"))) {
// flat-file persistencepersistence = new PersistenceFlatFile( this);
} else {// database persistencepersistence = new PersistenceDatabase( this);
}
// listeners; previous slides}
This tests against the default value of the config.yml
TestPlugin.java
Persistence Abstraction
If you offer more than one implementation (a way) to persist data, define the behaviour with an interfacepackage org.me.bukkit.persist;
public interface IPersistence {
// load a valueString loadSomething(String key);
// save a valuevoid saveSomething(String key, String value);
// notify implementations, that the plugin goes down// time to store any pending changesvoid shutdown();
} We continue with the persistence topic after the main class methods are finished
To simplify things, we'll only store a key/value pair.
IPersostence.java
onDisable()
Shutdown persistence implementation. Everything else (listeners) is handled by bukkit
@Overridepublic void onDisable() {
// notify persistence implementation persistence.shutdown();// free some memorypersistence = null;
}
TestPlugin.java
onCommand()
Answer player commands
@Override// only necessary, if your plugin offers in-game commandspublic boolean onCommand(final CommandSender sender,
final Command command, final String label, final String[] args) {
// answer user commands
// we only registered one command, no need to check anything elsesender.sendMessage( "Hello world!");// "true" means, we answered the commandreturn true;
// if we can not answer the command, have the super class // try to handle it// return super.onCommand(sender, command, label, args);
}
TestPlugin.java
getDatabaseClasses()
Register beans with bukkit
@Overridepublic List<Class<?>> getDatabaseClasses() {
// register database beans/pojosList<Class<?>> classes = new LinkedList<Class<?>>();
// add all beans hereclasses.add(TestBean. class);// ... add other beans
// return the complete listreturn classes;
}Bukkit will handle persistence for the class(es)
TestPlugin.java
Bean example
JPA annotation define how to store data@Entity@Table(name = "table_name")public class TestBean {
@Id private Long id;
@Column private String value;
@Column private String key;
public Long getId() { return id; }public String getKey() { return key; }public String getValue() { return value; }
public void setId(final Long id) { this.id = id; }public void setKey(final String key) { this.key = key; }public void setValue(final String value) { this.value = value; }
}
@Entity marks the class as a data container
@Table allows to state the DB table name
@Id marks a property as technical key
Any property with @Column will be persisted(@Id is also persisted)
A property needs a getter and a setter
TestBean.java
What have we done so far?
● We enabled database management for the plugin
● We defined a bean to hold data● We registered that bean with bukkit to store
and retrieve the data to/from a database
No we can start working with the database objects.
● Query the database● Configure the database connection
Persistence Implementation (1/4)
Before we do anything else, we have to ensure that the tables existpublic class PersistenceDatabase implements IPersistence {
private final TestPlugin plugin;
public PersistenceDatabase(TestPlugin plugin) {this.plugin = plugin;checkDDL();
}
private void checkDDL() {try {
// check access to tableplugin.getDatabase().find(TestBean. class).findRowCount();
} catch (PersistenceException e) {// error means tables does not exist, create itplugin.installDDL();
}}
}
Have bukkit create the tables. (This method is not visibly by default.)
Count the rows of one entity
PersistenceDatabase.java
Persistence Implementation (2/4)
Store a value.
public void saveSomething(final String key, final String text) {
// create a new bean that is managed by bukkitTestBean bean = plugin.getDatabase()
.createEntityBean(TestBean. class);
// fill the bean with values to store// since bukkit manages the bean, we do not need to set// the ID propertybean.setKey(key);bean.setValue(text);
// store the beanplugin.getDatabase().save(bean);
}
PersistenceDatabase.java
Persistence Implementation (3/4)
Query a value
public String loadSomething( final String key) {// create a query that returns TestBean objectsQuery<TestBean> query = plugin.getDatabase().find(TestBean. class);// formulate the query "select * from table_name where key = {0}"query.where().eq( "key", key);// limit the amount of rows returnedquery.setMaxRows(1);// execute the queryList<TestBean> beans = query.findList();// process the resultif (beans == null || beans.size() == 0) {
// nothing found; value hasn't been storedreturn null;
} else {// found something; return the value of the 1st result objectreturn beans.get(0).getValue();
}}
PersistenceDatabase.java
Persistence Implementation (4/4)
Get notified when the plugin is shutting down
public void shutdown() {// The database implementation of the IPersistence does // not need to take any actions when the plugin goes down.
// So this method remains empty.
// A FlatFile implementation, however, may now have to // store any values that were only held in memory // until this moment.
}
PersistenceDatabase.java
Database Connection
Do not forget to configure the database connection in bukkit.yml!
# default config entry# - one DB per plugindatabase: username: bukkit isolation: SERIALIZABLE driver: org.sqlite.JDBC password: walrus url: jdbc:sqlite:{DIR}{NAME}.db
# SQLite - one DB for all pluginsdatabase: username: bukkit isolation: SERIALIZABLE driver: org.sqlite.JDBC password: walrus url: jdbc:sqlite:data/bukkit.db
# MySQL - one DB for all pluginsdatabase: username: bukkit isolation: SERIALIZABLE driver: com.mysql.jdbc.Driver password: walrus url: jdbc:mysql://localhost:3306/bukkit
Conclusion
You should now have a basic understanding● How bukkit manages databases● How you can use them to store data● How to retrieve data
You've also seen● How to manage default configuration files● How to (de)register listeners● How to register and process in-game
commands
Test Project Code
Have a look at the code:https://dl.dropbox.com/u/17379347/snipets/test-plugin.zip
The code was not tested and does not do much else than illustrating the techniques describe on the previous slides.
References
● Minecraft (www.minecraft.net)A certain sandbox game
● Mojang (www.mojang.com)The company @notch founded that continues to develop and improve Minecraft
● Bukkit (www.bukkit.org)An extensible server implementation and API for Minecraft