29
Bukkit's Database Engine And some remarks regarding plugin development by Urs P. Stettler Image Source

Bukkit's Database Engine

  • Upload
    cryxli

  • View
    2.839

  • Download
    0

Embed Size (px)

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.

Citation preview

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

Java Slang

● BeanPOJO implementing EJB properties (getters & setters)

● JPA: Java persistence APIInterface how to store java objects

● ORM: Object relational mappingWay to squeeze objects into relational databases

● POJO: Plain old java objectA java class that does not contain (much) logic