GameAnvil Server API - Java doc
IDE: Intellij 2022.3.3
JDK: openjdk version "11.0.16.1" 2022-08-12 LTS
GameAnvil 1.4.1
DB
MySQL 8.0.23
Redis
GitリポジトリでcloneしたプロジェクトをIntelliJで実行します。
基本設定はGradle設定Dependenciesにcom.nhn.gameanvil:gameanvil:1.4.1-jdk11でJDK11バージョンが使われています。
resources/GameAnvilConfig.jsonファイルにIPが127.0.0.0.1になっています。
サンプルサーバーは基本JDK11に設定されているので、IntelliJが他のバージョンに設定されている場合、設定をJDK11に合わせるとビルド時にエラーが発生しません。
もし、IntelliJでGameAnvilライブラリとJDKのバージョンを合わせずに実行すると、次のようなエラーが発生します。
Exception in thread "main" java.lang.UnsupportedClassVersionError: co/paralleluniverse/fibers/SuspendExecution has been compiled by a more recent version of the Java Runtime (class file version 54.0), this version of the Java Runtime only recognizes class file versions up to 52.0
IntelliJ JDKの設定は以下をご確認ください。
File > Project Structure > Project Settings > ProjectメニューでJDKを確認します。
IntelliJ IDEA > Settings > Build, Execution, Deployment > Buil Tools > Gradle > Gradle JVMメニューでJDKを確認します。
Gradle JDKを変更したら、GradleタブのReloadを実行してプロジェクトに反映します。
ビルド環境設定は下記の内容を順番に設定します。IntelliJのバージョンによって画面は少し違う場合があります(スクリーンショットは2023.12バージョンです)。
パスワードなしでRedisに接続する場合は、com.nhn.gameanvil.sample.common.GameConstants
クラスのRedis接続情報を修正して接続します。
// Redis接続情報
public static final String REDIS_URL = "接続アドレス";
public static final int REDIS_PORT = 7500;
パスワード情報が設定されている場合は、Redisに接続する際にcom.nhn.gameanvil.sample.redis.RedisHelper
クラスのコメントアウトされた部分のパスワード設定を使って接続します。
/**
* Redis接続処理。使用前に最初に1回呼び出し、接続が必要
*
* @param url接続url
* @param port接続port
* @throws SuspendExecutionこのメソッドはファイバーをsuspendできることを意味します。
*/
public void connect(String url, int port) throws SuspendExecution {
// Redis接続処理
RedisURI clusterURI = RedisURI.Builder.redis(url, port).build();
// パスワードが必要な場合はパスワード設定を追加してRedisURIを作成
// RedisURI clusterURI = RedisURI.Builder.redis(url, port).withPassword("password").build();
this.clusterClient = RedisClusterClient.create(Collections.singletonList(clusterURI));
this.clusterConnection = Lettuce.connect(GameConstants.REDIS_THREAD_POOL, clusterClient);
if (this.clusterConnection.isOpen()) {
logger.info("============= Connected to Redis using Lettuce =============");
}
this.clusterAsyncCommands = clusterConnection.async();
}
サンプルサーバーでは基本的にJasync-sqlを基本的に使用し、StoredProcedureでクエリを使います。
Mybatis接続時、com.nhn.gameanvil.sample.common.GameConstants.USE_DB_JASYNC_SQL
の値をfalseに変更すると、DBがmybatisで動作します。
サンプルで使っているテーブル情報です。
CREATE TABLE `users` (
`uuid` varchar(40) NOT NULL,
`login_type` int(11) NOT NULL,
`app_version` varchar(45) DEFAULT NULL,
`app_store` varchar(45) DEFAULT NULL,
`device_model` varchar(45) DEFAULT NULL,
`device_country` varchar(45) DEFAULT NULL,
`device_language` varchar(45) DEFAULT NULL,
`nickname` varchar(45) DEFAULT NULL,
`heart` int(11) NOT NULL,
`coin` bigint(15) DEFAULT '0',
`ruby` bigint(15) DEFAULT '0',
`level` int(11) DEFAULT '1',
`exp` bigint(15) DEFAULT '0',
`high_score` bigint(15) DEFAULT '0',
`current_deck` varchar(45) NOT NULL,
`create_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
使用しているStoredProcedure情報です。
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_users_insert $$
CREATE PROCEDURE sp_users_insert
(
pi_uuid VARCHAR(40),
pi_login_type INT,
pi_app_version VARCHAR(45),
pi_app_store VARCHAR(45),
pi_device_model VARCHAR(45),
pi_device_country VARCHAR(45),
pi_device_language VARCHAR(45),
pi_nickname VARCHAR(45),
pi_heart INT,
pi_coin BIGINT,
pi_ruby BIGINT,
pi_level INT,
pi_exp BIGINT,
pi_high_score BIGINT,
pi_current_deck VARCHAR(45)
)
BEGIN
DECLARE err INT default '0';
DECLARE continue handler for SQLEXCEPTION set err = -1;
START TRANSACTION;
INSERT INTO users (uuid, login_type, app_version, app_store, device_model, device_country, device_language, nickname, heart, coin, ruby, level, exp, high_score, current_deck, create_date, update_date)
VALUES (pi_uuid, pi_login_type, pi_app_version, pi_app_store, pi_device_model, pi_device_country, pi_device_language, pi_nickname, pi_heart, pi_coin, pi_ruby, pi_level, pi_exp, pi_high_score, pi_current_deck, NOW(), NOW());
SELECT ROW_COUNT();
IF err < 0 THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END $$
DELIMITER ;
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_users_select_uuid $$
CREATE PROCEDURE sp_users_select_uuid
(
pi_uuid VARCHAR(40)
)
BEGIN
SELECT *
FROM users
WHERE uuid = pi_uuid;
END $$
DELIMITER ;
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_users_update_high_score $$
CREATE PROCEDURE sp_users_update_high_score
(
pi_uuid VARCHAR(40),
pi_high_score BIGINT
)
BEGIN
DECLARE err INT default '0';
DECLARE continue handler for SQLEXCEPTION set err = -1;
START TRANSACTION;
UPDATE users
SET high_score = pi_high_score, update_date = NOW()
WHERE uuid = pi_uuid;
SELECT ROW_COUNT();
IF err < 0 THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END $$
DELIMITER ;
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_users_update_current_deck $$
CREATE PROCEDURE sp_users_update_current_deck
(
pi_uuid VARCHAR(40),
pi_current_deck VARCHAR(45)
)
BEGIN
DECLARE err INT default '0';
DECLARE continue handler for SQLEXCEPTION set err = -1;
START TRANSACTION;
UPDATE users
SET current_deck = pi_current_deck, update_date = NOW()
WHERE uuid = pi_uuid;
SELECT ROW_COUNT();
IF err < 0 THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END $$
DELIMITER ;
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_users_update_nickname $$
CREATE PROCEDURE sp_users_update_nickname
(
pi_uuid VARCHAR(40),
pi_nickname VARCHAR(45)
)
BEGIN
DECLARE err INT default '0';
DECLARE continue handler for SQLEXCEPTION set err = -1;
START TRANSACTION;
UPDATE users
SET nickname = pi_nickname, update_date = NOW()
WHERE uuid = pi_uuid;
SELECT ROW_COUNT();
IF result < 0 THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END $$
DELIMITER ;
Jasync-sqlを使用すると、com.nhn.gameanvil.sample.common.GameConstants
クラスのDB接続情報を修正して接続します。
// DB接続情報
public static final String DB_USERNAME = "ユーザー名";
public static final String DB_HOST = "ホスト名";
public static final int DB_PORT = 3306;
public static final String DB_PASSWORD = "パスワード";
public static final String DB_DATABASE = "データベース名";
public static final int MAX_ACTIVE_CONNECTION = 30;
Mybatis接続はresources/mybatid-config.xmlの接続設定を修正して接続します。
<!-- MySQL接続情報を指定します。 -->
<properties>
<property name="hostname" value="ホスト名" />
<property name="portnumber" value="3306" />
<property name="database" value="データベース名" />
<property name="username" value="ユーザー名" />
<property name="password" value="パスワード" />
</properties>
GradleタブのrunMain実行で、IntelliJで実行"gameanvil.sample-game-server [runMain]"
先に設定しておいた「SampleGameServer」構成を使ってサーバーを実行します。
サーバーが正常に動作されると、下記のように全てのノードに対してonReadyログが出力されます。
http://127.0.0.1:18400/management/nodeInfoPageページでローカルで実行されたノードの状態を確認できます。全てのノードがREADYになったら、正常に実行されたということです。
構成はGameNode 4, GatewayNode 4, SupportNode2, IpcNode 1, ManagementNode 1, Locationnode 2, LocationNookupNode1, MatchNode 1, GatewayNetworkNode 1, SupportNetwotNode1合計18個ノードが表示されます。
正常にサーバーが実行されない場合、設定を再確認するか、logのエラー部分を確認してお問い合わせください。
DBやRedisの場合、サンプルサーバーに適用された部分はチーム内部で使う部分なので、直接構築して指定する必要があります。
DBやRedisの設定がない場合、サンプルサーバーが正常に動作しません。
GameAnvilバージョン確認
dependencies {
api 'com.nhn.gameanvil:gameanvil:1.4.1-jdk11'
}
build.gradle設定
import java.nio.file.Paths
plugins {
id 'java'
id 'java-library'
}
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
group = 'com.nhn.gameanvil'
version = '1.4.1'
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
repositories {
mavenLocal()
mavenCentral()
}
configurations {
quasar
api.setCanBeResolved(true)
all {
resolutionStrategy {
force 'com.esotericsoftware:kryo:4.0.2'
}
}
}
// standalone jarを作成します。
jar {
baseName = "sample_game_server"
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
manifest {
attributes 'Main-Class': 'com.nhn.gameanvil.sample.Main'
}
from {
configurations.compileClasspath.collect {
it.isDirectory() ? it : zipTree(it)
}
}
}
// GameAnvilサーバーを実行します。
task runMain(dependsOn: build, type: JavaExec) {
jvmArgs = [
"-Xms6g",
"-Xmx6g",
"-XX:+UseG1GC"]
main = 'com.nhn.gameanvil.sample.Main'
classpath = sourceSets.main.runtimeClasspath
}
compileJava {
dependsOn.processResources
doLast {
ant.taskdef(name: 'instrumentation', classname: 'co.paralleluniverse.fibers.instrument.InstrumentationTask', classpath: configurations.api.asPath)
ant.instrumentation(verbose: 'true', check: 'true', debug: 'true') {
fileset(dir: 'build/classes/') {
include(name: '**/*.class')
}
}
}
}
dependencies {
api files(Paths.get(project.projectDir.absolutePath, './src/main/resources/META-INF/quasar-core-0.8.0-jdk11.jar').toString())
api 'org.mybatis:mybatis:3.5.3'
api 'mysql:mysql-connector-java:8.0.23'
api 'com.nhn.gameanvil:gameanvil:1.4.1-jdk11'
}
GradleタブのGameAnvilTutorial > Tasks > build > jarでプロジェクトをビルドします。
正常にビルドが完了したら、プロジェクトフォルダbuild/libsにビルドされたjarファイルが作成されます。
Commandでサーバーを駆動するためにはGradleでビルドされたsample_game_server-1.4.1.jarを使用します。
java -Xms6g -Xmx6g -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+UseStringDeduplication -jar sample_game_server-1.4.1.jar
実行すると、IntelliJで実行したようにonReadyログが表示され、http://127.0.0.1:18400/management/nodeInfoPageページに全てのノードがREADYと表示されたら正常起動されたということです。
ゲーム開発の参考用にGameAnvilサンプルクライアントと連動するために作ったプロジェクトです。
ゲームサーバーに接続する前にゲームセッション情報を呼び出すためのサービスです。
ローンチ情報リクエストパケット処理
http://127.0.0.1:18600/launching?platform=Editor&appStore=GOOGLE&appVersion=1.2.0&deviceId=4D34C127-9C56-5BAB-A3C2-D8F18C0B7B6E形式でリクエストします。
渡されたデータを解析して確認し、GatewayNodeサーバーのIP、PORTを返します。
Authentication.proto:認証、ログイン
GameMulti.proto:マルチゲーム
GameSingle.proto:シングルゲーム
Result.proto:レスポンスコード
User.proto:ユーザー
プラグインがインストールされたら、次のようにbuild.batファイルを右クリックして次のようなコマンドでintelliJで直接変換できます。
メインクラスのGameAnvilServerを設定して実行します。設定時にクライアントとのプロトコルを同じ順番で登録し、サーバーで使うスレッドプールを作成します。
GameAnvilServer gameAnvilServer = GameAnvilServer.getInstance();
// クライアントと転送するプロトコルの定義 - 順番はクライアントと同じでなければなりません。
gameAnvilServer.addProtoBufClass(Authentication.getDescriptor());
gameAnvilServer.addProtoBufClass(GameMulti.getDescriptor());
gameAnvilServer.addProtoBufClass(GameSingle.getDescriptor());
gameAnvilServer.addProtoBufClass(Result.getDescriptor());
gameAnvilServer.addProtoBufClass(User.getDescriptor());
// ゲームで使用するDBスレッドプール指定
gameAnvilServer.createExecutorService(GameConstants.DB_THREAD_POOL, 100);
// ゲームで使用するRedisスレッドプールを指定
gameAnvilServer.createExecutorService(GameConstants.REDIS_THREAD_POOL, 100);
// annotationクラス登録処理のためのscan packageを指定
gameAnvilServer.addPackageToScan("com.nhn.gameanvil.sample");
// サーバー実行
gameAnvilServer.run();
GameAnvilで使用できるようにGatewayNode, GameNode, SupportNode, MatchMaker, Roomは@annotaionでクラスと名前を指定する必要があります。サーバーが実行される時、宣言されたクラスはエンジンで自動的にクラス登録処理が行われます。
BaseConnectionを継承して実装したコネクションクラスに宣言します。
@Connection()
BaseGatewayNodeを継承して実装したゲートウェイノードに宣言します。
@GatewayNode()
BaseSessionを継承して実装したセッションに宣言します。
@Session()
BaseGameNodeを継承して実装したゲームノードにGameAnvilConfig.jsonに設定したゲームノードのサービス名を指定します。
@ServiceName(GameConstants.GAME_NAME)
BaseUserを継承して実装したゲームユーザーにゲームのサービス名とユーザータイプを指定します。ユーザータイプはクライアントに接続して作成されるタイプです。
@ServiceName(GameConstants.GAME_NAME)
@UserType(GameConstants.GAME_USER_TYPE)
BaseRoomを継承して実装したルームにゲームサービス名とルームタイプを指定します。ルームタイプはクライアントから接続するルームタイプを指定します。
// シングルゲーム
@ServiceName(GameConstants.GAME_NAME)
@RoomType(GameConstants.GAME_ROOM_TYPE_SINGLE)
// ルームマッチゲーム
@ServiceName(GameConstants.GAME_NAME)
@RoomType(GameConstants.GAME_ROOM_TYPE_MULTI_ROOM_MATCH)
// ユーザーマッチゲーム
@ServiceName(GameConstants.GAME_NAME)
@RoomType(GameConstants.GAME_ROOM_TYPE_MULTI_USER_MATCH)
BaseUserMatchMaker, BaseRoomMatchMakerを継承して実装したマッチメーカーには、該当ルームと同じサービス名とルームタイプを指定します。
BaseSupportNodeを継承して実装したサポートノードに、GameAnvilConfig.jsonに設定したサポートノードのサービス名を指定します。
@ServiceName(GameConstants.SUPPORT_NAME_LAUNCHING)
ゲームコンテンツで定義して処理するパケットとして、クライアントとやり取りするパケットを登録します。処理するクラスは、登録した種類に合わせたパケットハンドラーインターフェイスを実装する必要があります。
ユーザーがログインした状態で処理するパケット: com.nhn.gameanvil.sample.game.user.GameUser
static private PacketDispatcher packetDispatcher = new PacketDispatcher();
static {
packetDispatcher.registerMsg(User.ChangeNicknameReq.getDescriptor(), CmdChangeNicknameReq.class); // ニックネーム変更プロトコル
packetDispatcher.registerMsg(User.ShuffleDeckReq.getDescriptor(), CmdShuffleDeckReq.class); // デッキシャッフルプロトコル
packetDispatcher.registerMsg(GameSingle.ScoreRankingReq.getDescriptor(), CmdSingleScoreRankingReq.class); // シングルスコアランキング
}
// 処理するクラスはimplements IPacketHandler<GameUser> を実装して作る必要があります。
クライアントからrequestでリクエストされたパケットについては、クライアントでレスポンスを待っているので、サーバーで処理して渡されたユーザーオブジェクトを経由してgameUser.reply()でレスポンス処理をする必要があります。
ルーム内にいるときに処理するパケット: com.nhn.gameanvil.sample.game.multi.usermatch.SnakeRoom
private static RoomPacketDispatcher dispatcher = new RoomPacketDispatcher();
static {
dispatcher.registerMsg(GameMulti.SnakeUserMsg.getDescriptor(), CmdSnakeUserMsg.class); // ユーザー位置情報
dispatcher.registerMsg(GameMulti.SnakeFoodMsg.getDescriptor(), CmdSnakeRemoveFoodMsg.class); // food削除情報処理
}
// 処理するクラスはimplements IRoomPacketHandler<SnakeRoom, GameUser>を実装して作る必要があります。
サーバーからクライアントへ渡すパケットはgameUser.send()でレスポンスを待たずに送信します。
restパケット: com.nhn.gameanvil.sample.support.LaunchingSupport
private static RestPacketDispatcher restMsgHandler = new RestPacketDispatcher();
static {
// launching
restMsgHandler.registerMsg("/launching", RestObject.GET, CmdLaunching.class);
}
// 処理するクラスはimplements IRestPacketHandlerを実装して作る必要があります。
restリクエストに対しては、受け取ったrestObject.writeString()でレスポンスメッセージを渡します。
com.nhn.gameanvil.sample.redis.RedisHelper
接続処理
private RedisClusterClient clusterClient;
private StatefulRedisClusterConnection<String, String> clusterConnection;
private RedisAdvancedClusterAsyncCommands<String, String> clusterAsyncCommands;
/**
* Redis接続、使用前に最初に1回呼び出して接続する必要があります。
*
* @param url接続url
* @param port接続port
* @throws SuspendExecution
*/
public void connect(String url, int port) throws SuspendExecution { // Redis接続処理
RedisURI clusterURI = RedisURI.Builder.redis(url, port).build();
// パスワードが必要な場合、パスワード設定を追加してRedisURIを作成します。
// RedisURI clusterURI = RedisURI.Builder.redis(url, port).withPassword("password").build();
this.clusterClient = RedisClusterClient.create(Collections.singletonList(clusterURI));
this.clusterConnection = Lettuce.connect(GameConstants.REDIS_THREAD_POOL, clusterClient);
this.clusterAsyncCommands = clusterConnection.async();
}
終了処理
/**
* 接続終了サーバがダウンする前に呼び出されなければならない。
*/
public void shutdown() {
clusterConnection.close();
clusterClient.shutdown();
}
使用
/**
* ユーザーデータをRedisに保存
*
* @param gameUserInfoユーザー情報
* @return保存の成否
* @throws SuspendExecution
*/
public boolean setUserData(GameUserInfo gameUserInfo) throws SuspendExecution {
String value = GameAnvilUtil.Gson().toJson(gameUserInfo);
boolean isSuccess = false;
try {
Lettuce.awaitFuture(clusterAsyncCommands.hset(REDIS_USER_DATA_KEY, gameUserInfo.getUuid(), value)); // この戻り値は最初に設定した時だけtrueで、値を更新する時はfalseでレスポンス
isSuccess = true;
} catch (TimeoutException e) {
logger.error("setUserData - timeout", e);
}
return isSuccess;
}
com.nhn.gameanvil.sample.db.jasyncsql.JAsyncSqlManager
接続処理
// JAsyncSQL接続
public JAsyncSqlManager(String username, String host, int port, String password, String database, int maxActiveConnection) {
jAsyncSql = new JAsyncSql(new com.github.jasync.sql.db.Configuration(
username,
host,
port,
password,
database), maxActiveConnection);
logger.info("JAsyncSqlManager::JAsyncSql connect");
}
終了処理
// JAsyncSQL終了
public void close() {
jAsyncSql.disconnect();
}
使用
/**
* ユーザーの最高スコアを保存
*
* @param uuid ユーザーのユニークな識別子
* @param highScore修正する最高スコア
* @return修正されたレコード数を返す
* @throws TimeoutExceptionこの呼び出しに対してタイムアウトが発生する可能性があることを意味します。
* @throws SuspendExecutionこのメソッドはファイバーをsuspendできることを意味します。
*/
public int updateUserHigScore(String uuid, int highScore) throws TimeoutException, SuspendExecution {
String sql = "CALL sp_users_update_high_score( '"
+ uuid + "', "
+ highScore + ")";
QueryResult queryResult = jAsyncSql.execute(sql);
return getStoredProcedureRowCount(queryResult);
}
DB接続情報設定: resources/maybatis-config.xml
<!-- MySQL接続情報を指定する。 -->
<properties>
<property name="hostname" value="ホスト名" />
<property name="portnumber" value="3306" />
<property name="database" value="データベース名" />
<property name="username" value="ユーザー名" />
<property name="password" value="パスワード" />
<property name="poolPingQuery" value="select 1"/>
<property name="poolPingEnabled" value="true"/>
<property name="poolPingConnectionsNotUsedFor" value="3600000"/>
</properties>
使うクエリ登録 - 外部xmlを使う場合、下記のコメント部分を参考にして使います。
<mappers>
<!-- 定義されたSQL構文をマッピングします。基本的にリソースの中にあるmapper.xmlを使う時 -->
<mapper resource="query/UserDataMapper.xml"/>
<!-- 外部で指定されたmapper.xmlファイルを指定する場合は、フルパス指定を使用します。 -->
<!--<mapper url="file:///C:/_KevinProjects/GameServerEngine/sample-game-server/target/query/UserDataMapper.xml"/>-->
</mappers>
クエリ: resources/query/UserDataMapper.xml
<select id="selectUserByUuid" resultType="com.nhn.gameanvil.sample.mybatis.dto.UserDto">
SELECT uuid,
login_type AS loginType,
app_version AS appVersion,
app_store AS appStore,
device_model AS deviceModel,
device_country AS deviceCountry,
device_language AS deviceLanguage,
nickname,
heart,
coin,
ruby,
level,
exp,
high_score AS highScore,
current_deck AS currentDeck,
create_date AS createDate,
update_date AS updateDate
FROM users
WHERE uuid = #{uuid}
</select>
DB接続設定: com.nhn.gameanvil.sample.db.mybatis.GameSqlSessionFactory
/**
* ゲームで使用するDB接続オブジェクト
*/
public class GameSqlSessionFactory {
private static Logger logger = LoggerFactory.getLogger(GameSqlSessionFactory.class);
private static SqlSessionFactory sqlSessionFactory;
/** XMLで指定された接続情報を読み込みます。 */
// クラス初期化ブロック:クラス変数の複雑な初期化に使用します。
// クラスが初めてロードされるときに一度だけ実行されます。
static {
// 接続情報を指定しているXMLのパスを読み込む
try {
// mybatis_config.xmlファイルのパスを指定
String mybatisConfigPath = System.getProperty("mybatisConfig"); // パラメータが渡された場合、サーバー(実行時に-DmybatisConfig=オプションで指定)
logger.info("mybatisConfigPath : {}", mybatisConfigPath);
if (mybatisConfigPath != null) {
logger.info("load to mybatisConfigPath : {}", mybatisConfigPath);
InputStream inputStream = new FileInputStream(mybatisConfigPath);
if (sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
} else { // パラメータを渡さない場合、内部ファイルから設定を取得する。
Reader reader = Resources.getResourceAsReader("mybatis/mybatis-config.xml");
logger.info("load to resource : mybatis/mybatis-config.xml");
// sqlSessionFactoryが存在しない場合は作成する。
if (sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* データベース接続オブジェクトを使ってDATABASEに接続したセッションを返す。
*/
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
実行時、-DmybatisConfig=を使ってない場合、sessionを作る時、次のようにビルド時に入った内部保存環境ファイルに設定されるログが記録されます。
[INFO ] [GameAnvil-DB_THREAD_POOL-0] [GameSqlSessionFactory.java:30] mybatisConfigPath : null
[INFO ] [GameAnvil-DB_THREAD_POOL-0] [GameSqlSessionFactory.java:39] load to resource : mybatis-config.xml
実行時に-DmybatisConfig=を使用した場合、sessionを作成する時、次のように指定された位置情報に設定されるログが記録されます。
[INFO ] [GameAnvil-DB_THREAD_POOL-0] [GameSqlSessionFactory.java:30] mybatisConfigPath : .\src\main\resources\mybatis-config.xml
[INFO ] [GameAnvil-DB_THREAD_POOL-0] [GameSqlSessionFactory.java:32] load to mybatisConfigPath : .\src\main\resources\mybatis-config.xml
使用: com.nhn.gameanvil.sample.db.mybatis.UserDbHelperService
/**
* ユーザー情報DBに保存
*
* @param gameUserInfoユーザー情報を伝達
* @return保存されたレコード数
* @throws TimeoutException
* @throws SuspendExecution
*/
public int insertUser(GameUserInfo gameUserInfo) throws TimeoutException, SuspendExecution { // Callable形式でAsync実行し、結果を返す。
Integer resultCount = Async.callBlocking(GameConstants.DB_THREAD_POOL, new Callable<Integer>() {
@Override
public Integer call() throws Exception {
SqlSession sqlSession = GameSqlSessionFactory.getSqlSession();
try {
UserDataMapper userDataMapper = sqlSession.getMapper(UserDataMapper.class);
int resultCount = userDataMapper.insertUser(gameUserInfo.toDtoUser());
if (resultCount == 1) { // 単件保存なので、1つなら正常にDB commit
sqlSession.commit();
}
return resultCount;
} finally {
sqlSession.close();
}
}
});
return resultCount;
}
{
//-------------------------------------------------------------------------------------
// 共通情報。
"common": {
"ip": "127.0.0.1", // ノードごとに共通で使用するIP (マシンのIPを指定)
"meetEndPoints": ["127.0.0.1:18000"], // 対象ノードのcommon IPとcommunicatePortを登録。 (該当サーバーendpointを含めることができ、リストで複数指定可能)
"debugMode": false // デバッグ時に各種タイムアウトが発生しないようにするオプション。リアルでは必ずfalseでなければなりません。
},
//-------------------------------------------------------------------------------------
// LocationNode設定
"location": {
"clusterSize": 1, // 合計何台のマシン(VM)で構成されるか?
"replicaSize": 1, // レプリケーショングループのサイズ(master + slaveの数)
"shardFactor": 2 // shardingのための引数(下記のコメント参照)
// 全体shardの数= clusterSize x replicaSize x shardFactor
// 1つのマシン(VM)で駆動するshardの個数= replicaSize x shardFactor
// 固有のshardの総数(master shardの数) = clusterSize x shardFactor
},
// マッチノード設定
"match": {
"nodeCnt": 1
},
//-------------------------------------------------------------------------------------
// クライアントとのコネクションを管理するノード。
"gateway": {
"nodeCnt": 4, // ノード数(ノード番号は0から付与される)。
"ip": "127.0.0.1", // クライアントと接続するIP。
"dns": "", // クライアントと接続するドメインアドレス。
"connectGroup": { // コネクションの種類。
"TCP_SOCKET": {
"port": 18200, // クライアントと接続するポート。
"idleClientTimeout": 240000 // データの送受信がない状態以降のタイムアウト(0なら使用しない)。
},
"WEB_SOCKET": {
"port": 18300,
"idleClientTimeout": 0
}
}
},
//-------------------------------------------------------------------------------------
// ゲームロビーの役割をするノード(ゲームルーム、ユーザーを含む)。
"game": [
{
"nodeCnt": 4,
"serviceId": 1,
"serviceName": "TapTap",
"channelIDs": ["","","","",""], // ノードごとに付与するチャンネルID(一意である必要はない。" "文字列でチャンネルを区別せずに重複して使うこともできる)。
"userTimeout": 5000 // disconnect後のユーザーオブジェクト除去タイムアウト。
}
],
"support": [
{
"nodeCnt": 2,
"serviceId": 2,
"serviceName": "Launching",
"restIp": "127.0.0.1",
"restPort": 18600
}
]
}
loggerをパッケージ名単位で分けて指定できます。別々に指定すると、そのパッケージ名は指定されたレベルで適用され、指定されなければrootで指定された設定で適用されます。
<logger name="com.nhn.gameanvil" level="INFO"/>
<logger name="com.nhn.gameanvil.sample" level="DEBUG"/>
<root>
<level value="WARN"/>
<appender-ref ref="ASYNC"/>
<appender-ref ref="STDOUT"/>
</root>