Low Level Database Access: Difference between revisions
imported>Hendrik Brummermann No edit summary |
imported>Hendrik Brummermann No edit summary |
||
| (46 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
{{Navigation for Marauroa Top}} |
{{Navigation for Marauroa Top|Internals}} |
||
{{Navigation for Marauroa Developers}} |
{{Navigation for Marauroa Developers}} |
||
| Line 23: | Line 23: | ||
== Writing your own DAO == |
== Writing your own DAO == |
||
Please stick to the following rules when writing your own DAO classes |
|||
* use <nowiki>[variables]</nowiki> for untrusted data because it will be escaped automatically to prevent SQL Injection Attacks |
|||
* register your class in DAORegistry (see next section) instead of calling the constructor directly |
|||
* use dbTransaction.getLastInsertId() instead of "select @@identity" and call it directly after the insert in question |
|||
* try to avoid database system specific code (pay special attention to generally supported SQL functions) |
|||
* provide all methods with two signatures: One with DBTransaction as first parameter and one without. The second one should just get the transaction itself, call the first one, and commit/rollback the transaction |
|||
== Extending a provided DAO == |
== Extending a provided DAO == |
||
DAO classes should never be instantiated directly. Instead you should (and marauroa does) use the DAORegistry. This allows you to write a subclass of a DAO provided by marauroa and register it instead. If you are familiar with Spring, this is a similar concept. But without all the bulk of xml configuration files, parameter injection and interfaces with only one single implementation. |
|||
| ⚫ | |||
Imagine you want to subclass the CharacterDAO with your class SomeGameCharacterDAO: |
|||
<source lang="java"> |
|||
public class SomeGameCharacterDAO extends CharacterDAO { |
|||
... |
|||
</source> |
|||
You simply register it as |
|||
<source lang="java"> |
|||
DAORegistry.get().register(CharacterDAO.class, new SomeGameCharacterDAO()); |
|||
</source> |
|||
Note: In the register call the first parameter is the parent class you want to replace. |
|||
== Adding support for another database system == |
|||
{{TODO|Clean this up: |
|||
Marauroa tries to stick closely to the SQL standard. It is, however, not possible to write SQL statements that work on all common database systems because each database system has its own dialect. Luckily there are only minor differences. For example MySQL requires "create table" statements to end in "TYPE=InnoDB". Marauroa uses an interface [http://stendhal.game-host.org/hudson/job/marauroa_HEAD/javadoc/marauroa/server/db/adapter/DatabaseAdapter.html DatabaseAdapter] to hide those differences. |
|||
There is already a default implementation provided for this interface called [http://stendhal.game-host.org/hudson/job/marauroa_HEAD/javadoc/marauroa/server/db/adapter/AbstractDatabaseAdapter.html AbstractDatabaseAdapter] ([http://arianne.cvs.sf.net/viewvc/arianne/marauroa/src/marauroa/server/db/adapter/AbstractDatabaseAdapter.java?view=markup source]). And we provide a MySQLDatabaseAdapter ([http://arianne.cvs.sf.net/viewvc/arianne/marauroa/src/marauroa/server/db/adapter/MySQLDatabaseAdapter.java?view=markup source]) and a H2DatabaseAdapter ([http://arianne.cvs.sf.net/viewvc/arianne/marauroa/src/marauroa/server/db/adapter/H2DatabaseAdapter.java?view=markup source]), too. |
|||
JDBC technology is an API that lets you access virtually any tabular data source from the Java programming language. It provides cross-DBMS connectivity to a wide range of relational databases. Unfortunatally it does not hide vendor specific stuff. Marauroa and Stendhal currently only work with MySQL because of that. Adding support for other database software would be very easy as the database specific code is concentrated in the classes JDBCDatabase (Marauroa) and StendhalPlayerDatabase (Stendhal). We have no need for that, so it was not done, yet. We will, however, accept patches for multi database system support, so if you need it, go ahead. |
|||
Just have a look at those java files. It should be very easy to add more adapters for other database systems. Note: We are very interested in accepting those adapters and adding them to the main code base. |
|||
| ⚫ | |||
You need to download MySQL Connector/J in order to get it to run: http://www.mysql.com/downloads/api-jdbc-stable.html |
|||
If you add tables and columns it is a good practice to create them automatically on server start. The method runSQLScript() in the class JDBCHelper can be used to execute an SQL script. Note: You should use "create table if not exists" in stead of simple "create table" so that you can execute the script on every server start without having to worry about whether the table already exists or not. |
|||
To configure Marauroa to work with a JDBC source, run the appropriate GenerateINI program. For Stendhal this is games.stendhal.server.core.engine.GenerateINI. |
|||
Unfortunately there is no "create column if not exists" clause in SQL. So if we have the need to add columns to existing tables, we will need to do the check ourselves. |
|||
Before using the application with the database, you need to create the database itself. So, with MySQL just run MySQL and enter: |
|||
<pre> |
|||
create database stendhal; |
|||
grant all on stendhal.* to stendhal_user@localhost identified by 'stendhal_passwd'; |
|||
</pre> |
|||
The following example shows the code that was used to add the new result column to the passwordChange table. Before that column was added, only successful password changes were logged. So the result column should be initialized to 1. |
|||
The rest of code is handled by the server itself, and it will create the tables if they don't exist. |
|||
<source lang="java"> |
|||
| ⚫ | |||
if (!transaction.doesColumnExist("passwordChange", "result")) { |
|||
transaction.execute("ALTER TABLE passwordChange ADD COLUMN (result TINYINT);", null); |
|||
transaction.execute("UPDATE passwordChange SET result=1 WHERE result IS NULL", null); |
|||
| ⚫ | |||
</source> |
|||
{{#breadcrumbs: [[Marauroa]] | [[Navigation for Marauroa Developers|Internals]] | [[Low Level Database Access|Low Level Database Access]] }} |
|||
Latest revision as of 23:29, 18 September 2010
- Stendhal
This article describes how Marauroa accesses the database internally and how you can add support for your own tables. The table structure of the Marauroa database is explained in Marauroa Database Structure. You might want to have a look at High Level Database Access first. It explains the high level API to access the database from your program code.
Database abstraction
The level code database access code is encapsulated in DAO classes. Only these classes use JDBC to send SQL queries to the database.
You start a transaction by obtaining a DBTransaction object from the TransactionPool. The you use the methods of the DBTransaction object to talk to the database. Note that many methods expect a parameter map as second parameter. This allows you to use [variables] in your SQL statement without having to worry about escaping input parameters to prevent SQL injection attacks.
DBTransaction, however, does not execute the SQL statements itself. It internally forwards them to a subclass of DatabaseAdapter. This is a very tiny abstraction layer of to hide SQL dialects used by different database systems.
Writing your own DAO
Please stick to the following rules when writing your own DAO classes
- use [variables] for untrusted data because it will be escaped automatically to prevent SQL Injection Attacks
- register your class in DAORegistry (see next section) instead of calling the constructor directly
- use dbTransaction.getLastInsertId() instead of "select @@identity" and call it directly after the insert in question
- try to avoid database system specific code (pay special attention to generally supported SQL functions)
- provide all methods with two signatures: One with DBTransaction as first parameter and one without. The second one should just get the transaction itself, call the first one, and commit/rollback the transaction
Extending a provided DAO
DAO classes should never be instantiated directly. Instead you should (and marauroa does) use the DAORegistry. This allows you to write a subclass of a DAO provided by marauroa and register it instead. If you are familiar with Spring, this is a similar concept. But without all the bulk of xml configuration files, parameter injection and interfaces with only one single implementation.
Imagine you want to subclass the CharacterDAO with your class SomeGameCharacterDAO: <source lang="java">
public class SomeGameCharacterDAO extends CharacterDAO {
...
</source>
You simply register it as <source lang="java">
DAORegistry.get().register(CharacterDAO.class, new SomeGameCharacterDAO());
</source> Note: In the register call the first parameter is the parent class you want to replace.
Adding support for another database system
Marauroa tries to stick closely to the SQL standard. It is, however, not possible to write SQL statements that work on all common database systems because each database system has its own dialect. Luckily there are only minor differences. For example MySQL requires "create table" statements to end in "TYPE=InnoDB". Marauroa uses an interface DatabaseAdapter to hide those differences.
There is already a default implementation provided for this interface called AbstractDatabaseAdapter (source). And we provide a MySQLDatabaseAdapter (source) and a H2DatabaseAdapter (source), too.
Just have a look at those java files. It should be very easy to add more adapters for other database systems. Note: We are very interested in accepting those adapters and adding them to the main code base.
Updating the database structure
If you add tables and columns it is a good practice to create them automatically on server start. The method runSQLScript() in the class JDBCHelper can be used to execute an SQL script. Note: You should use "create table if not exists" in stead of simple "create table" so that you can execute the script on every server start without having to worry about whether the table already exists or not.
Unfortunately there is no "create column if not exists" clause in SQL. So if we have the need to add columns to existing tables, we will need to do the check ourselves.
The following example shows the code that was used to add the new result column to the passwordChange table. Before that column was added, only successful password changes were logged. So the result column should be initialized to 1. <source lang="java"> if (!transaction.doesColumnExist("passwordChange", "result")) {
transaction.execute("ALTER TABLE passwordChange ADD COLUMN (result TINYINT);", null);
transaction.execute("UPDATE passwordChange SET result=1 WHERE result IS NULL", null);
} </source> {{#breadcrumbs: Marauroa | Internals | Low Level Database Access }}
