Sei sulla pagina 1di 29

Bukkit's Database Engine

And some remarks regarding plugin development

by 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


Bukkit API is available as a maven artifact.

Your plugin's main class Your default configuration Your plugin description

Your maven Project Object Model

Maven configuration (1/3)

pom.xml

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> Name your plugin

Import the bukkit API

Maven configuration (2/3)


pom.xml continued

pom.xml

Tell maven how to get bukkit <repositories> <repository> <id>bukkit</id> <name>bukkit</name> <url>http://repo.bukkit.org/content/repositories/releases/ </url> </repository> </repositories> Some settings about the project <properties> <jdk>1.6</jdk> <project.build.sourceEncoding> UTF-8</project.build.sourceEncoding> <main.class> org.me.bukkit.TestPlugin </main.class> </properties> Your plugin's main class

Maven configuration (3/3)


pom.xml continued

pom.xml

<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> Tell maven how to build the project </build> </project>

Maven will replace place-holders in the config files

plugin.yml (wiki)
This file tells bukkit how to handle your plugin.
Maven will replace these # plugin config for bukkit name: ${project.name} main: ${main.class} version: ${project.version} description: > ${project.name} is a test plugin for bukkit. database: true commands: hello: description: Say hello. 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 plugin SomeKey: 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 class


package org.me.bukkit; public class TestPlugin extends JavaPlugin { @Override public List<Class<?>> getDatabaseClasses() {}

TestPlugin.java

@Override public boolean onCommand( final CommandSender sender, final Command command, final String label, final String[] args) {} @Override public void onDisable() {} @Override public void onEnable() {} }

On the next slides we go through each of these methods.

onEnable() (1/3)

TestPlugin.java

How to load the configuration and complete it with default values


@Override public void onEnable() { Load saved config from disk // read config FileConfiguration config = getConfig(); // complete with default config Complete missing entries in config with config.options().copyDefaults( true); the ones from the default config.yml // load data from config // ... // save config You can always access the config with saveConfig(); getConfig() . Bukkit keeps it in memory. No need to copy the values elsewhere. // persistence // listeners } Save the now completed config to disk

onEnable() (2/3)
Create and register listeners
private DeathListener deathListener ; @Override public void onEnable() { // config; from previous slide // persistence; next topic // create listeners deathListener = new DeathListener( this); // register register PluginManager pm = getServer().getPluginManager(); pm.registerEvents( deathListener , this); }

TestPlugin.java

Event Listener example

DeathListener.java

The Event type of the method indicate what events the listeners expects
This interfaces marks the class as a listener public class DeathListener implements Listener { private final TestPlugin plugin; public DeathListener( final TestPlugin plugin) { this.plugin = plugin; } The annotation marks the method as event listener @EventHandler (priority = EventPriority. NORMAL) public void onDeath(final EntityDeathEvent event) { // do something useful } } The method parameter indicates the event type

onEnable() (3/3)

TestPlugin.java

Init the persistence implementation according to the plugin config


private IPersistence persistence ; @Override public void onEnable() { // config; from earlier This tests against the default value of the config.yml

// create persistence instance if ("FlatFile" .equalsIgnoreCase(config.getString( "Storage"))) { // flat-file persistence persistence = new PersistenceFlatFile( this); } else { // database persistence persistence = new PersistenceDatabase( this); } // listeners; previous slides }

Persistence Abstraction

IPersostence.java

If you offer more than one implementation (a way) to persist data, define the behaviour with an interface
package org.me.bukkit.persist; public interface IPersistence { // load a value String loadSomething(String key); // save a value void saveSomething(String key, String value); // notify implementations, that the plugin goes down // time to store any pending changes void 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.

onDisable()

TestPlugin.java

Shutdown persistence implementation. Everything else (listeners) is handled by bukkit


@Override public void onDisable() { // notify persistence implementation persistence .shutdown(); // free some memory persistence = null; }

onCommand()
Answer player commands
@Override // only necessary, if your plugin offers in-game commands public boolean onCommand( final CommandSender sender, final Command command, final String label, final String[] args) { // answer user commands

TestPlugin.java

// we only registered one command, no need to check anything else sender.sendMessage( "Hello world!" ); // "true" means, we answered the command return true ; // if we can not answer the command, have the super class // try to handle it // return super .onCommand(sender, command, label, args); }

getDatabaseClasses()
Register beans with bukkit
@Override public List<Class<?>> getDatabaseClasses() { // register database beans/pojos List<Class<?>> classes = new LinkedList<Class<?>>(); // add all beans here classes.add(TestBean. class); // ... add other beans // return the complete list return classes; }

TestPlugin.java

Bukkit will handle persistence for the class(es)

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;

TestBean.java

@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

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; } }

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)


PersistenceDatabase.java

Before we do anything else, we have to ensure that the tables exist


public class PersistenceDatabase implements IPersistence { private final TestPlugin plugin; public PersistenceDatabase(TestPlugin plugin) { this.plugin = plugin; checkDDL(); } Count the rows of one entity private void checkDDL() { try { // check access to table plugin.getDatabase().find(TestBean. class).findRowCount(); } catch (PersistenceException e) { // error means tables does not exist, create it plugin.installDDL(); } Have bukkit create the tables. (This } method is not visibly by default.)

Persistence Implementation (2/4)


PersistenceDatabase.java

Store a value.
public void saveSomething( final String key, final String text) { // create a new bean that is managed by bukkit TestBean 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 property bean.setKey(key); bean.setValue(text); // store the bean plugin.getDatabase().save(bean); }

Persistence Implementation (3/4)


PersistenceDatabase.java

Query a value
public String loadSomething( final String key) { // create a query that returns TestBean objects Query<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 returned query.setMaxRows(1); // execute the query List<TestBean> beans = query.findList(); // process the result if (beans == null || beans.size() == 0) { // nothing found; value hasn't been stored return null ; } else { // found something; return the value of the 1st result object return beans.get(0).getValue(); } }

Persistence Implementation (4/4)


PersistenceDatabase.java

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. }

Database Connection
Do not forget to configure the database connection in bukkit.yml!
# default config entry # - one DB per plugin database: username: bukkit isolation: SERIALIZABLE driver: org.sqlite.JDBC password: walrus url: jdbc:sqlite:{DIR}{NAME}.db # SQLite - one DB for all plugins database: username: bukkit isolation: SERIALIZABLE driver: org.sqlite.JDBC password: walrus url: jdbc:sqlite:data/bukkit.db

# MySQL - one DB for all plugins database: 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

Java Slang
Bean
POJO implementing EJB properties (getters & setters)

JPA: Java persistence API


Interface how to store java objects

ORM: Object relational mapping


Way to squeeze objects into relational databases

POJO: Plain old java object


A java class that does not contain (much) logic

Potrebbero piacerti anche