jdbcとjpaにおける ビッグ・データのベスト・プ …cs.registeroutparameter(2,...

Post on 12-Jul-2020

5 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016

21

//big data /

アプリケーションでビッグ・データを扱う場合、避けるべき罠がいくつかあります。小規模または中規模のデータベースで何の問題も

なく動作していたアプリケーションが、データベースのサイズが増加した途端に動作しなくなることがあります。大量データを扱うアプリケーションで障害が発生する原因は、メモリ、データベース・トランザクションのパフォーマンスの悪化、不適切なアーキテクチャなど、多岐にわたる場合があります。JDBCを使う場合でも、オブジェクト・リレーショナル・マッピング・フレームワークを使う場合でも、アプリケーションのアーキテクチャによってその成否が分かれることがあります。 本記事では、ビッグ・データのプレッシャーに負けないアプリケーショ

ンを作るために、JDBCやJava Persistence API(JPA)でデータを扱う際のいくつかのベスト・プラクティスを紹介します。ビッグ・データを扱う独自仕様のAPIまたはフレームワークや、標準のRDBMSまたはNoSQLで利用できる特定のデータベース・オプションには触れません。そういった点ではなく、環境設定やコードのチューニングの基本戦略と、Javaアプリケーションで大量データを扱う際のベスト・プラクティスについて説明します。

何よりも重要なストアド・プロシージャ次の問いについて考えてみてください。なぜあなたはアプリケーションに大量データを取り込んでいるのでしょうか。「大量の結果セットを対象に計算や分析などの処理を行おうとしているから」と答えた方は、その技法を考え直してみた方がよいかもしれません。ほとんどのデータベース(特にRDBMS)には、データベース内でデータ処理を直接行うさまざまな組込み関数やプロシージャが含まれています。一方、多くのNoSQLデータベースでは、ストアド・プロシージャは提供されていません。何ら

かのタイプの関数やストアド・コード機能が提供されているNoSQLデータベースは多いものの、大抵の場合、標準的なデータベースで提供されるものほど高機能ではありません。多くのデータベース・ソリューションには、データベース内で直接実行可能なプロシージャを開発できる言語が含まれています。たとえば、Oracle Databaseには、SQL言語を安全に拡張したPL/SQLという独自の手続き言語が含まれています。ビッグ・データを扱う際に、アプリケーションではなくデータベースで分析処理を実行すると、パフォーマンス面で大きなメリットを得ることができます。ただし、データベースの外部でしか実行できない分析処理要件がなければの話であることは言うまでもありません。 JDBCのAPIとJPAのいずれにも、データベースのストアド・プロシージ

ャを呼び出すソリューションが含まれています。ストアド・プロシージャを使用して値のやり取りをするのも簡単です。この方法の優れている点は、1つのコネクションでプロシージャを呼び出して数千件のレコードを処理できることです。リスト1に、JDBCまたはJPAのいずれかを利用してデータベースのストアド・プロシージャを呼び出す方法を示します。

リスト1:// JDBCを利用してデータベースのストアド・プロシージャ // を呼び出す CallableStatement cs = null;

try { cs = conn.prepareCall("{call DUMMY_PROC(?,?)}"); cs.setString(1, "This is a test"); cs.registerOutParameter(2, Types.VARCHAR); cs.executeQuery();

JOSH JUNEAU

JDBCとJPAにおける ビッグ・データのベスト・プラクティス大量データに圧倒されない基礎を身につける

ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016

22

//big data /

// 結果を処理する String returnStr = cs.getString(2);

} catch (SQLException ex){ ex.printStackTrace(); }

// JPAを利用してデータベースのストアド・プロシージャを呼び出す// @NamedStoredProcedureQueryをエンティティ・クラスに追加する@NamedStoredProcedureQuery( name="createEmp", procedureName="CREATE_EMP", parameters = { @StoredProcedureParameter( mode= ParameterMode.IN, type=String.class, name="first"), @StoredProcedureParamter( mode = ParameterMode.IN, type=String.class, name="last")})

// ストアド・プロシージャを呼び出すStoredProcedureQuery qry = em.createStoredProcedureQuery("createEmp");qry.setParameter("first", "JOSH");qry.setParameter("last","JUNEAU");qry.execute();

多くのデータベース・ソリューションでは、Javaで書かれたストアド・プロシージャもサポートされています。Javaによるストアド・プロシージャをデータベース内で動作させること以上に、Javaのプログラミング・スキルを活用できる方法はないでしょう。ただし、この方法にはトレードオフもあります。Javaによるストアド・プロシージャは、アプリケーション内ではなくデータベース内にあるにもかかわらず、処理を行うためには、データにアクセスするためのコネクション・コードが依然として必要となります。そのため、Javaによるストアド・プロシージャのコーディングは、データベース用の言語でコーディングするよりも面倒なものになる可能性が

あります。 当然、あらゆるデータベース駆動型アプリケーションは、最終的に何

らかの方法によってデータを直接処理する必要があります。しかし、データベース内で実行できる処理と、本当にJavaアプリケーション内で実行する必要がある処理との違いは、意識しておいてください。

正しい設定を行う通常、アプリケーション・サーバーのデフォルトの設定は、平均的なI/Oを行うアプリケーションに適したものになっています。大量データを扱うアプリケーションで使う場合、パフォーマンスに影響する可能性がある設定を事前にすべて見直して適切に設定する必要があります。 アプリケーション固有の設定:デフォルトで、JDBCドライバはフェッチするたびに一定数の行を取得します。たとえば、Oracle Databaseでは、フェッチするデフォルトの行数は10行です。すなわち、1,000行を返そうとする場合、アプリケーションはすべての行を取得するために100回のフェッチ操作を行う必要があるということです。それでは、この処理を1,000回行う場合を考えてみてください。大きなボトルネックになることがわかるでしょう。JDBCのsetFetchSize()を使うと、フェッチすべき行数についてヒントを与えることができます。リスト2に、ヒントを指定する方法を示します。ゼロを指定すると、JDBCドライバは値を無視します。コードからわかるように、Statementを作成した後、希望のフェッチ・サイズを設定するだけです。同様に、ResultSetのフェッチ・サイズも指定できます。この値は、Statementのフェッチ・サイズよりも優先されます。

リスト2:String qry = "select …";CreateConnection.loadProperties();issuesList = new ArrayList();try (Connection conn = CreateConnection.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(qry);) {

特定のデータ・セットを複数回処理する必要がある場合、データをキャッシュすることで、コネクションを簡単に管理できます。

ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016

23

//big data / stmt.setFetchSize(300);

while (rs.next()) ... }} catch (SQLException e) { // 例外をログに記録する}

JPAでも、同じ設定を行うことができます。JPAでは、フェッチ・サイズを明示的に設定するのではなく、パフォーマンスを向上させるために希望の行数をフェッチするようにJDBCドライバを「ガイド」するヒントを与えることができます。リスト3のコードは、EclipseLinkドライバを使った場合のJPAの問合せでフェッチ・サイズを指定する方法を示しています。この例では、フェッチ・サイズを文字列値で指定しています。

リスト3:public List<DukeIssues> findAllConfigureFetchSize( String fetchSize){ Query qry = em.createQuery( "select object(o) from DukeIssues o"); qry.setHint( "eclipselink.JDBC_FETCH_SIZE", fetchSize); return qry.getResultList();}

さまざまな値でビッグ・データ・アプリケーションをテストし、もっとも効果的な値を特定することは欠かせません。フェッチ・サイズを大きく設定すると、アプリケーションで必要となるメモリの量に影響することに気を付けてください。フェッチ・サイズを設定する際は十分注意し、アプリケーション・サーバーのメモリの量も適切となるよう構成してください。大量データの処理に影響し得る、その他のJDBCドライバ設定にも配慮してください。コネクション管理:コネクションの作成には、非常にコストがかかる場合があります。そのため、データベースを扱う場合は、必要とするコネクション数を抑えるとよいでしょう。つまり、アプリケーションは、それぞれのコネクションでわずかな量の処理しか行わないような無駄なことをせず、各コネクションを最大限に活用するべきだということです。特定のデ

ータ・セットを複数回処理する必要がある場合、可能な限りデータをキャッシュすることで、コネクションを簡単に管理できます。大量の挿入や更新、削除を行う場合は、更新のたびにコネクションのオープンと破棄を繰り返すのではなく、トランザクションや一括更新を実行する必要があるかもしれません。 一般的に、アプリケーション・サーバーが管理するコネクション・プールは、データ管理において重要な役割を果たします。通常、コネクション・プールには、アプリケーション・サーバーが準備するデフォルトのサイズがあります。すべてのコネクションが使われると、アプリケーション・サーバーはさらにコネクションを要求します。同様に、使われていないコネクションはプールに戻されます。コネクション・プールのサイズをアプリケーションが使用するコネクション数に合わせて適切に設定することは、良好なパフォーマンスを発揮する上で欠かせません。 コネクション・プールを設定する方法はいくつかあります。通常、アプリケーション・サーバーのコネクション・プールは、管理コンソールやXML、コマンドライン・ユーティリティで設定できます。たとえば、GlassFishの場合は、管理コンソールまたはasadminコマンドライン・ユーティリティを使用してコネクション・プールの設定を変更できます。リスト4は、このユーティリティを使用してJDBCコネクション・プールとJDBCコネクション・プール・リソースを作成する方法を示しています。

リスト4:asadmin create-jdbc-connection-pool \--datasourceclassname \ oracle.jdbc.pool.OracleDataSource \--restype javax.sql.DataSource \--property user=dbuser:password=dbpassword:url= "jdbc:oracle\:thin\:@localhost\:1521\:MYDB" \jdbc_conn-pool

asadmin create-jdbc-connection-pool \

大量データをキャッシュする場合、メモリについても注意しなければなりません。そのため、環境に問題がないことを確認する必要があります。

ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016

24

//big data / --connectionpoolid myjdbc_oracle-pool jdbc/resource

[編集注:‒-property user=行とその次の行は、1行として入力する必要があります]プールからコネクションを取得するためには、javax.sql.ConnectionPoolDataSourceインタフェースを使用します。ConnectionPoolDataSourceは、PooledConnectionオブジェクトを返し、そこからConnectionを取得できます。通常、ConnectionPoolDataSourceはJava Naming and Directory Interfaceのコネクション・オブジェクトによって実装されており、リスト5のようにしてgetConnection()メソッドを呼び出すとPooledConnectionオブジェクトを取得できます。JPAを使用している場合、EntityManagerがコネクションの取得を制御しますが、永続性ユニット内にいくつかの設定が必要となる場合があります。

リスト5:ConnectionPoolDataSource cpds = (ConnectionPoolDataSource) initialCtx.lookup(jndiName);PooledConnection pooledConn = ConnectionPoolDataSource.getConnection();Connection conn = pooledConn.getConnection();

もう1点注意すべきことに、分離レベルがあります。分離レベルは、データベースがどのようにデータの整合性を維持するかを示しています。分離レベルが低いと、データベースの整合性が低下します。各分離レベルにはそれぞれのユースケースがあり、分離レベルの選択によってはパフォーマンスが向上する場合もあります。各分離レベルがアプリケーションに与える影響を学び、もっとも適したものを選択してください。表1に、分離レベルの一覧を示します。整合性がもっとも低いものから順に並んでおり、パフォーマンスへの影響と併せて記載しています。本記事では、各分離レベルの詳しい説明は行いませんが、分離レベルはパフォーマンスとデータの整合性に影響し得る要因であるため、ここで紹介しました。

データ・アクセス・コードのベスト・プラクティスアプリケーションと環境の設定を適切なものとしたら、次に潜在的な問題の兆候を探すべき場所は、問合せおよび文のコードです。アプリケーションのコーディングを行う際に従うべきベスト・プラクティスはいくつかあります。大量データを処理する場合、とりわけデータを処理するコードはすべて、アプリケーションのパフォーマンスに大きな影響を与える可能性があります。最初に気を付けるべき点は、データのキャッシュです。アプリケーショ

ンが読取り専用の大量レコードを扱う場合は、データベースからフェッチするのではなく、データをキャッシュしてメモリから高速にアクセスすると効果的です。大量データをキャッシュする場合、メモリについても当然注意しなければなりません。そのため、環境に問題がないことを確認する必要があります。コーディングの際にもう1つ気を付けるべき重要な点は、アプリケーションの要件に適したJDBCコードを使用することです。ここでは、大量データにアクセスするためのJDBCおよびJPAの問合せをコーディングする際に注意すべきいくつかの点について詳しく取り上げます。また、更新操作や削除操作の管理方法によっては、アプリケーションが悪影響を受ける可能性があります。このセクションでは、一括操作を行う際のテクニックもいくつか紹介します。データのキャッシュ:アプリケーションで同じデータを複数回扱う必要がある場合、その都度データベースとの間を往復するのではなく、データをメモリに格納しておくと効果的です。頻繁に使用するデータをメモリにキャッシュする方法はいくつか存在します。その1つが、Hazelcastなどのインメモリ・データ・グリッドの使用です。また、組込みのJDBCキャッシュ・ソリューションを使うという選択肢もあります。この方法は、データ・グリッドと比べると、無駄は少ないものの堅牢性では劣ります。

表1:トランザクション分離レベル

分離レベル パフォーマンスへの影響TRANSACTION_NONE 最速

TRANSACTION_READ_UNCOMMITTED 最速

TRANSACTION_READ_COMMITTED 高速

TRANSACTION_REPEATABLE_READ 中速

TRANSACTION_SERIALIZABLE 低速

ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016

25

//big data /

インメモリ・データ・グリッドを使用すると、分散型のマップ、リスト、キューに簡単にデータを格納でき、データベースとの間を何度も往復することなくデータを繰り返し活用できます。このソリューションは特に、簡単に使い始めることができますが、データの拡張、パーティショニング、ロードバランシングなど、豊富なオプションを備えており、高度な用途にも十分使用できます。さらにすばらしいことに、Payaraには組込みのHazelcastソリューションが搭載されています。ベアメタルJDBCキャッシュの利用を検討している場合は、javax.sql.CachedRowSetなどのJDBCソリューションを使うと、少量のデータを格納して繰り返しアクセスすることができます。CachedRowSet内のデータは、変更して後ほどデータベースと同期させるという使い方もできます。CachedRowSetは、RowSetFactoryまたはCachedRowSetのデフォルト・コンストラクタから生成できます。その後、CachedRowSetオブジェクト用にConnectionを設定し、問合せを含む文字列ベースのSQLコマンドを渡します。更新を行うためにこのオブジェクトを使用する場合は、表の主キーを指定する必要があります。最後に文を実行すると、データが返されます。リスト6に、CachedRowSetの使用方法を示します。

リスト6:RowSetFactory factory;

try {// RowSetFactoryを作成する factory = RowSetProvider.newFactory();// ファクトリを使用してCachedRowSetオブジェクトを作成する crs = factory.createCachedRowSet();// 必要に応じて、CachedRowSet接続文字列を // 設定する// crs.setUsername(username);// crs.setPassword(password);// crs.setUrl(jdbc_url);// 問合せを設定する

crs.setCommand("select id, request_date, "+ "priority, description from duke_issues");// キー列を設定する int[] keys = {1}; crs.setKeyColumns(keys);// 問合せを実行する crs.execute(conn);// 切断状態のオブジェクトの内容を // 操作する while (crs.next()) { // 結果セットを操作する }} catch (SQLException ex) { ex.printStackTrace();}

JDBCでのPreparedStatementの使用:JDBCを使ってコードを記述している場合、通常のStatementではなくPreparedStatementを使うことが何よりも重要になります。PreparedStatementはプリコンパイルが可能であるため、複数回実行される場合、そのたびに再コンパイルする必要がなくなります。また、アプリケーションのパフォーマンス向上だけでなく、セキュリティ面のメリットも得られます。PreparedStatementには、SQLインジェクションに対する保護が組み込まれているためです。リスト7に、PreparedStatementを利用してResultSetを取得する一般的な例を示します。

リスト7:public List<DukeIssues> queryIssues(String assignedTo) { String qry = "SELECT ID, REQUEST_DATE, PRIORITY, DESCRIPTION " + "FROM DUKE_ISSUES " + "WHERE assigned_to = ?";

List<DukeIssues> issueList = new ArrayList(); try (Connection conn = CreateConnection.getConnection(); PreparedStatement stmt = conn.prepareStatement(qry))

JDBCドライバは、環境に適したものを利用することが重要です。

ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016

26

//big data / { stmt.setString(1, assignedTo); try (ResultSet rs = stmt.executeQuery();) { while (rs.next()) { int id = rs.getInt("ID"); java.util.Date requestDate = rs.getDate("REQUEST_DATE"); int priority = rs.getInt("PRIORITY"); String description = rs.getString("DESCRIPTION"); DukeIssues issue = new DukeIssues(); issue.setId(id); issue.setRequestDate(requestDate); issue.setPriority(priority); issue.setDescription(description); issueList.add(issue); } } } catch (SQLException e) { e.printStackTrace(); } return issueList;}

当然かもしれませんが、StatementやPreparedStatementは、使い終わったら忘れずにクローズしてください。クローズすることによって、ガベージ・コレクタがメモリを再利用できるようになります。一括操作の利用:アプリケーションの後続処理で大量の更新や削除を行う際は、一括操作が最適な場合があります。JDBCもJPAも、必要な場合に一括で書込み操作や削除操作を行う手段を提供しています。アプリケーションにとって一括操作が有用かどうかを完全に理解するために、一括操作の仕組みを理解しておくことが重要です。一括書込みには、パラメータを利用した一括書込みと動的一括書込みの2種類があります。 パラメータを利用した一括書込みは、基本的には、多数の同じ挿入、更新、削除を実行するもので、パラメータにバインド変数を使用して連続して実行します。つまり、一括操作全体を通して変わる部分はパラメータのみであり、操作のSQL自体は変わらないということです。その後、一連の連続操作は1回の呼出しでデータベースに送信され、まとめて実行

されます。パラメータを利用した一括書込みを行うと、2つの点でパフォーマンスが向上します。各操作で同じ文が使われるためSQLを毎回解析する必要がない点と、すべてがまとめて送信されるため1つのネットワーク・コネクションだけが使われる点です。 動的一括書込みでは、一括操作の各SQL文に異なるSQLを含めることができます。そのため、複数の種類の文をまとめてデータベースに送信できます。この方法ではパラメータのバインドはできないため、文ごとにSQLを解析する必要があります。したがって、純粋にパフォーマンスが向上するのはデータベースのコネクション関連の部分のみとなります。そのため、パラメータを使用した一括書込みほどのメリットはないかもしれません。JDBCドライバでいずれのタイプの一括操作を使用するかを決定するためには、ある程度のテストや調査が必要です。JDBCには、いずれのタイプの一括操作を実行できるかを判断するために使用可能な標準APIが含まれています。JDBCだけで一括操作を行うためには、まずコネクションの自動コミットをfalseに設定します。デフォルトでは、各操作は自動的にコミットされます。しかし、自動コミットをfalseに設定すると、操作は実行されるものの、明示的にコミットが発行されるまでコミットされなくなります。リスト8に、一括操作で挿入と更新のグループを実行する簡単な例を示します。一度に大量の行が挿入されるビッグ・データ環境では、ループ構造の中で挿入文を発行できます。まずコネクションをオープンし、次にループして挿入を行い、最後にコミットしてコネクションをクローズします。

リスト8:List<DukeIssues> issueList = queryIssues("JUNEAU");

String insStmt1 = "insert into duke_issues (id, request_date," +"priority, description) values " +"(908472, '2016-01-01',0,'QUERY NOT WORKING')";

String insStmt2 = "insert

大量データを扱うアプリケーションを書くための第一歩は、データを管理することです。

ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016

27

//big data / into duke_issues " +"(id, request_date, priority, description) values " +"(908473, '2016-01-01',0,'RESULTS NOT POSTING')";

String updStmt = "insert duke_issues " +"set status = ? where assigned_to = ?";

try (Connection conn = getConnection(); Statement stmt = conn.createStatement();) { conn.setAutoCommit(false);

// 必要に応じてここでループを実行し、バッチ・ // トランザクションに文を追加する

stmt.addBatch(insStmt1); stmt.addBatch(insStmt2); stmt.addBatch(updStmt); int[] count = stmt.executeBatch();

conn.commit(); conn.setAutoCommit(true);} catch (SQLException e) { // 例外をログに記録する}

JPAを使った一括操作には、規定された標準的な方法は存在しません。しかし、ほとんどのJPAプロバイダは何らかのタイプの一括操作をサポートしています。通常、JPA環境で一括操作を有効にするためには、永続化ユニット内で設定を行う必要があります。ほとんどのJPAドライバは、パラメータを利用した一括書込みと動的一括書込みに対応しているため、適切に設定する必要があります。リスト9に、永続化ユニット内にEclipseLink向けの設定を行う方法を示します。

リスト9:<persistence-unit>...<property name="eclipselink.jdbc.batch-writing" value="JDBC"/><property name="eclipselink.jdbc.batch-writing.

size" value="1000"/>...</persistence-unit>

専用のドライバやAPIの検討:JDBCドライバは、環境に適したものを利用することが重要です。データベース・ベンダーがJDBCドライバを提供していることもありますが、アプリケーションのユースケースに対応した最適なものではない可能性があります。大量データの処理用に特別にチューニングされたドライバも数多く存在するため、そういったドライバを選ぶようにしてください。つまり、ジョブに最適なドライバを使えるように、データベース・プラットフォームや作業している環境についての調査を行う必要があります。

まとめ大量データを扱うアプリケーションを書くための第一歩は、特定のAPIの仕組みを掘り下げることというよりも、データを管理することです。データ管理戦略には、JDBC環境やJPA環境の適切な設定、最高のパフォーマンスを実現するためのコーディング、必要に応じたキャッシュの利用などが含まれます。拡張性のないアプリケーションやうまく動作しないアプリケーションを作ってしまわないように、正しく動作するアプリケーションの基礎を身につけてください。本記事では、基本的な点を説明しました。しかし、こういった点をきちんと実行に移した後は、所定のデータの負荷に対して可能な限り最高のパフォーマンスを得られるように、JDBC環境やJPA環境のパフォーマンスを恒常的に監視することが大切です。</article>

Josh Juneau(@javajuneau):アプリケーション開発者、システム・アナリスト、データベース管理者。Oracle Technology NetworkおよびJava Magazineの技術記事を執筆している。ApressからJavaやJava EEについての著書を出版しており、JSR 372およびJSR 378についてJCPの専門家グループのメンバーを務めている。

ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016

28

//big data /

OracleのJDBCチュートリアルOracleのJPAチュートリアルデータベース分離の背景

learn more

//user groups /

BARCELONA JUGスペインのバルセロナには、活気にあふれた誕生間もないエコシステムと、多くのソフトウェア関連ビジネス・コミュニティが存在します。バルセロナは美しい町で観光スポットも多く、市内には世界に誇るビーチもあります。ここでは、Javaテクノロジーに関する幅広い経験を持つチームを中心に設立された非営利団体Barcelona Java Users

Group(@barcelonajug)が活動しています。このグループは、2012年の設立以来、通常は月1回のペースでJavaのトピックを中心とした講演や会合を企画しています。集会のほとんどは英語で行われています。 今までに、開発者ツール、テスト技法、API設計、高パフォーマンス・

メッセージングなどのトピックを取り上げてきました。また、Stephen Chin(Oracle)、Claus Ibsen氏、Mario Fusco氏、Gavin King氏、Mauricio Salatino氏(Red Hat)、Jean-Baptiste Onofré氏(Talend)、Alex Soto氏(CloudBees)、Peter Kriens氏(OSGi Alliance)、Norberto Leite氏(MongoDB)などが講演を行っています。昨年は、Javaバルセロナ・カンファレンスという2日間の大きなイベント

を開催し、JavaやJVM、その関連テクノロジー、オープンソース・テクノロジーなどを取り上げました。そこでは、いくつかの国から開発者が集まり、独自環境におけるソフトウェア開発について学習、探究しました。2015年のイベントで行われた講演は、こちらからオンラインで閲覧できます。現在、このグループは6月16日から18日にかけて行われる今年のイベン

トを企画しています。取り上げるトピックや、人脈を作る機会が昨年より増えるとのことです。今年のイベントはポンペウ・ファブラ大学で開催され、参加型のワークショップも実施されます。詳細やチケットの購入については、こちらをご覧ください。Barcelona JUGの詳細については、MeetupまたはYouTubeをご覧くだ

さい。

top related