MySQLからRedisへのimport tool

この程度のことは何かしらの言語を使って、一瞬で作らなきゃだなぁ、と思って何が適当か少し考えてみた。

想定される制約条件は以下。

  • いろいろなサーバーで実行したい
  • Root権限が無いケースも想定
  • インストールされているのは Perl, Python, Java(Maven入り)
  • 入ってないのは Ruby, OCaml

と色々(もしかすると上記条件以外にも頭の片隅にある暗黙の制約があるかも)考えるとJavaが良さげ。

で、シンプルに書くとこんな感じかな?ということでメモ書き。

  • pom.xmlのdependencies
  <dependencies>
  	<dependency>
  		<groupId>mysql</groupId>
  		<artifactId>mysql-connector-java</artifactId>
  		<version>5.1.15</version>
  		<type>jar</type>
  		<scope>compile</scope>
  	</dependency>
  	<dependency>
  		<groupId>redis.clients</groupId>
  		<artifactId>jedis</artifactId>
  		<version>1.5.2</version>
  		<type>jar</type>
  		<scope>compile</scope>
  	</dependency>
  </dependencies>
  • Main.java(クラス名は適当, DBのユーザーとかも)
public class Main {
  public static void main(String[] args) throws SQLException {
    final String url = "jdbc:mysql://localhost/hogehoge";
    final Jedis jedis = new Jedis("localhost");
    Connection conn = DriverManager.getConnection(url, "root", "hogehoge");
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("select m.name, m.id, m.age, d.name from members m, depts d where d.id = m.dept_id");
    while (rs.next()) {
      String key = rs.getString("m.name");
      String val = "[age:" + rs.getInt("age") + "][dept:" + rs.getString("d.name") + "]";
      jedis.set(key, val);
    }
    rs.close();
    stmt.close();
    jedis.disconnect();
  }
}

実行はMavenのexec pluginを使うとこんな感じ。

$ mvn exec:java -Dexec.mainClass=com.komamitsu.importer.Main

Hibernate触ってみたメモ

そろそろ仕事がらこの辺りもからんできそうなので家事育児の隙をついて弄ってみました。という備忘録的メモ。まぁ実際はSpring経由でやりそうなんですが、まず直接使ってみたいなぁと。

方針は以下。


-1. 事前準備 => RDBMS(今回、MySQL)にDatabaseとTableを作っとく

mysql> desc depts;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(255) | YES  |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> select * from depts;
+----+-----------------+
| id | name            |
+----+-----------------+
|  1 | Development     |
|  2 | Sales           |
|  3 | Human Resources |
+----+-----------------+
3 rows in set (0.00 sec)

mysql> desc members;
+---------+-------------+------+-----+---------+-------+
| Field   | Type        | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id      | int(11)     | YES  |     | NULL    |       |
| name    | varchar(64) | YES  |     | NULL    |       |
| age     | int(11)     | YES  |     | NULL    |       |
| dept_id | int(11)     | YES  |     | NULL    |       |
+---------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> select * from members;
+------+------+------+---------+
| id   | name | age  | dept_id |
+------+------+------+---------+
|    1 | aaa  |   11 |       1 |
|    2 | bbb  |   12 |       1 |
|    3 | ccc  |   13 |       1 |
|    4 | ddd  |   21 |       2 |
|    5 | eee  |   22 |       2 |
|    6 | fff  |   23 |       2 |
|    7 | ggg  |   31 |       3 |
|    8 | hhh  |   32 |       3 |
|    9 | iii  |   33 |       3 |
+------+------+------+---------+
9 rows in set (0.04 sec)


0. Maven Projectの作成

mvnコマンド経由でも何でもいいんですが、今回はEclipseで作りました。普通に作ったので省略。


1. pom.xmlにdependenciesを書き書き

試行錯誤の末、こんな感じ。

  <dependencies>
  	<dependency>
  		<groupId>mysql</groupId>
  		<artifactId>mysql-connector-java</artifactId>
  		<version>5.1.15</version>
  		<type>jar</type>
  		<scope>compile</scope>
  	</dependency>
  	<dependency>
  		<groupId>log4j</groupId>
  		<artifactId>log4j</artifactId>
  		<version>1.2.16</version>
  		<scope>compile</scope>
  	</dependency>
  	<dependency>
  		<groupId>org.slf4j</groupId>
  		<artifactId>slf4j-log4j12</artifactId>
  		<version>1.4.2</version>
  		<type>jar</type>
  		<scope>compile</scope>
  	</dependency>
  	<dependency>
  		<groupId>org.hibernate</groupId>
  		<artifactId>hibernate-annotations</artifactId>
  		<version>3.4.0.GA</version>
  		<type>jar</type>
  		<scope>compile</scope>
  	</dependency>
  	<dependency>
  		<groupId>javassist</groupId>
  		<artifactId>javassist</artifactId>
  		<version>3.12.1.GA</version>
  		<type>jar</type>
  		<scope>compile</scope>
  	</dependency>
  </dependencies>

あまり良く解っていないのだけど、Logging周りがSlf4jというのになっていて、Log4jとのブリッジ的なものとしてslf4j-log4j12が必要っぽい。で、普通に入れると結構新しめなやつが入るのだけど、そうするとhibernate-coreが依存しているslf4j-apiとバージョン不一致を起こすので、一致させるようにしました。

あと、Hibernate-coreがこっそりとjavassistを使っているくせにpom上は依存していないみたいで、Runtimeでエラーが発生していたので、javassistも入れた、みたいな。


2. hibernate.cfg.xml

src/main/resources/hibernate.cfg.xmlを以下のように作成

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
    <property name="connection.url">jdbc:mysql://localhost/hogehoge</property>
    <property name="connection.username">root</property>
    <property name="connection.password">hogehoge</property>
    <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="dialect">org.hibernate.dialect.MySQLDialect</property>

    <property name="show_sql">true</property>

    <!-- JDBC connection pool (use the built-in) -->
    <property name="connection.pool_size">1</property>
    <property name="current_session_context_class">thread</property>
 
    <mapping class="com.komamitsu.model.Dept"/>
    <mapping class="com.komamitsu.model.Member"/>
  </session-factory>
</hibernate-configuration>

createとか書いていたら、プログラムを起動する度にtableが空になる恐怖を味わったりしました。


3. entity的なclass的なもの

package com.komamitsu.model;
import java.io.Serializable;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;


@Entity
@Table(name="depts")
public class Dept implements Serializable {
  @Id
  @GeneratedValue
  private int id;
  private String name;
  @OneToMany(mappedBy="dept")
  private Set<Member> members;
  
  /**
   * @return the id
   */
  public int getId() {
    return id;
  }
  /**
   * @param id the id to set
   */
  public void setId(int id) {
    this.id = id;
  }
  /**
   * @return the name
   */
  public String getName() {
    return name;
  }
  /**
   * @param name the name to set
   */
  public void setName(String name) {
    this.name = name;
  }
  /**
   * @return the members
   */
  public Set<Member> getMembers() {
    return members;
  }
  /**
   * @param members the members to set
   */
  public void setMembers(Set<Member> members) {
    this.members = members;
  }
  
  @Override
  public String toString() {
    return "id=" + id + ", name=" + name;
  }
}
package com.komamitsu.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name="members")
public class Member {
  @Id
  @GeneratedValue
  private int id;
  private String name;
  private int age;
  @ManyToOne
  @JoinColumn(name="dept_id")
  private Dept dept;
  /**
   * @return the id
   */
  public int getId() {
    return id;
  }
  /**
   * @param id the id to set
   */
  public void setId(int id) {
    this.id = id;
  }
  /**
   * @return the name
   */
  public String getName() {
    return name;
  }
  /**
   * @param name the name to set
   */
  public void setName(String name) {
    this.name = name;
  }
  /**
   * @return the age
   */
  public int getAge() {
    return age;
  }
  /**
   * @param age the age to set
   */
  public void setAge(int age) {
    this.age = age;
  }
  /**
   * @return the dept
   */
  public Dept getDept() {
    return dept;
  }
  /**
   * @param dept the dept to set
   */
  public void setDept(Dept dept) {
    this.dept = dept;
  }
  
  @Override
  public String toString() {
    return "id=" + id + ", name=" + name + ", age=" + age + ", dept=" + dept;
  }
}

@OneToMany, @ManyToOneとか使ってみたかったので使えて良かったなぁという。


4. main()

package com.komamitsu.runner;

import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.criterion.Restrictions;

import com.komamitsu.model.Dept;
import com.komamitsu.model.Member;

public class Runner {

  /**
   * @param args
   */
  public static void main(String[] args) {
    SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    
    Session session = sessionFactory.getCurrentSession();

    Transaction tx = session.beginTransaction();

    /*
    Dept dept = new Dept();
    dept.setName("Development");
    
    session.save(dept);
    */
    
    Criteria c = session.createCriteria(Dept.class);
    c.add(Restrictions.eq("name", "Sales"));
    List<Dept> list = c.list();
    System.out.println(list.size());
    
    for (Dept d : list)
      for (Member m : d.getMembers())
        System.out.println(m);
    
    tx.commit();

  }
}

まぁ普通な感じで。


5. 実行

Hibernate: select this_.id as id0_0_, this_.name as name0_0_ from depts this_ where this_.name=?
1
Hibernate: select members0_.dept_id as dept4_1_, members0_.id as id1_, members0_.id as id1_0_, members0_.age as age1_0_, members0_.dept_id as dept4_1_0_, members0_.name as name1_0_ from members members0_ where members0_.dept_id=?
id=6, name=fff, age=23, dept=id=2, name=Sales
id=4, name=ddd, age=21, dept=id=2, name=Sales
id=5, name=eee, age=22, dept=id=2, name=Sales

show_sql=trueなのでそれと混じっていますが、こんな感じで。


まぁなんというか普通ですよね... 普通のくせに何か長いな... とか。

途方にくれたときは mvn help:describe

最近、Mavenを勉強しているのだけど、何をすれば良いのか分からなくなり途方にくれることが良くある。特に疲れているとき。

そんな時は, mvn help:describe -Dcmd=install とか打つと何か光が見えるような気がしないでもない。まぁ lsを一定間隔で打つようなものか。

DBアクセスのサンプル

Spring frameworkを使うとDBまわりのコードがすっきりするらしいので、練習兼メモがてらサンプルを書いてみた。


RDBMSは何でも良いのだけどPostgreSQLで。適当なテーブルを用意しておく。

sample=# \c
psql (8.4.4)
You are now connected to database "sample".
sample=# \d users 
          Table "public.users"
 Column |       Type        | Modifiers 
--------+-------------------+-----------
 id     | integer           | 
 name   | character varying | 
 age    | integer           | 


ValueObject(っていうの?)はこんなの。

public class User {
	private final int id;
	private final String name;
	private final int age;
	
	public User(int id, String name, int age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}
	
	public int getId() {
		return id;
	}
	
	public String getName() {
		return name;
	}
	
	public int getAge() {
		return age;
	}
}


で、肝心のDAOはこんな感じ。PreparedStatementが無いし、毎度毎度の例外処理も無くてすっきり。JdbcDaoSupport、MappingSqlQuery、SqlUpdate 辺りのクラスが鍵。

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.object.MappingSqlQuery;
import org.springframework.jdbc.object.SqlUpdate;

public class UserDao extends JdbcDaoSupport {
	private static class FindUser extends MappingSqlQuery {
		private static final String SQL =
			"SELECT id, name, age FROM users";

		private FindUser(DataSource ds) {
			super(ds, SQL);
		}

		@Override
		protected User mapRow(ResultSet rs, int rowNum) throws SQLException {
			return new User(rs.getInt("id"), rs.getString("name"), rs.getInt("age"));
		}
	}
	
	private static class InsertUser extends SqlUpdate {
		private static final String SQL =
			"INSERT INTO users (id, name, age)" +
			"VALUES (NEXTVAL('seq_users_id'), ?, ?)";
		
		private InsertUser(DataSource ds) {
			super(ds, SQL);
			declareParameter(new SqlParameter("name", Types.VARCHAR));
			declareParameter(new SqlParameter("age", Types.INTEGER));
		}
	}
	
	private FindUser fu = null;
	private InsertUser iu = null;
	
	protected void initDao() {
		this.fu = new FindUser(getDataSource());
		this.iu = new InsertUser(getDataSource());
	}
	
	public List<User> findUser() {
		@SuppressWarnings("unchecked") List<User> users = fu.execute();
		return users;
	}
	
	public void insertUser(String name, int age) {
		Object[] params = { name, age };
		iu.update(params);
	}
}


で、これらを利用するコードはこちら。

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class Main {
	public static void main(String[] argv) {
		Resource resource = new ClassPathResource("applicationContext.xml");
		BeanFactory bf = new XmlBeanFactory(resource);
		UserDao udao = (UserDao) bf.getBean("userDao");
		udao.insertUser("Foo", 64);
		udao.insertUser("Bar", 72);
		
		for (User user : udao.findUser()) {
			System.out.printf("%3d: %s: %3d\n", user.getId(),
								 user.getName(), user.getAge());
		}
	}
}


あと、DIの設定はこれ。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName">
			<value>org.postgresql.Driver</value></property>
		<property name="url">
			<value>jdbc:postgresql://localhost/sample</value></property>
		<property name="username">
			<value>komamitsu</value></property>
	</bean>
	<bean id="userDao" class="UserDao">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
</beans>


これを実行すると。

  1: Foo:  64
  2: Bar:  72

となり成功。


ちょっと悔しいのは、UserDao.findUser()で警告を消すために@SuppressWarnings("unchecked")を使っていること。もう一回, Effective Javaジェネリクスの章を読み直して、再挑戦したい。

Mixin的なもの

普段使いのRubyでは、ModuleのMixinによって実装の継承ができて大変嬉しいのですが、Javaの場合はできなさそうで少し悲しいなぁ、と思っていたところSpring AOPをつかうとそれっぽいことができそうなので試してみました。

まず、Mixinのことなど何も知らないUserクラスがあるとします。nameしか情報を持っていません。

package com.hoge;

public class User {
	private String name;
	
	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
}

で、これに年齢に関する機能をつけたい場合、Spring AOPのIntroductionを使って、Ageインターフェースと...

package com.hoge;

public interface Age {
	void setAge(int age);
	int getAge();
}

AgeMixinクラスを作ります。

package com.hoge;

import org.springframework.aop.support.
       DelegatingIntroductionInterceptor;

public class AgeMixin extends DelegatingIntroductionInterceptor
		implements Age {

	private static final long serialVersionUID = 1L;
	private int age;

	@Override
	public int getAge() {
		return age;
	}

	@Override
	public void setAge(int age) {
		this.age = age;
	}
}

で、これらをくっつけます。

package com.hoge;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.
       DefaultIntroductionAdvisor;

public class Main {
	public static void main(String[] args) {
		ProxyFactory beanFactory = 
			new ProxyFactory(new Class[] { Age.class });
		beanFactory.setProxyTargetClass(true);
		beanFactory.addAdvisor(
                        new DefaultIntroductionAdvisor(new AgeMixin()));
		beanFactory.setTarget(new User());

		User user = (User) beanFactory.getProxy();
		user.setName("komamitsu");
		System.out.println(user.getName());

		((Age) user).setAge(18);
		System.out.println(((Age) user).getAge());
	}
}

setAge()を呼ぶときにAgeクラスにキャストしているところが悲しいですが、気を取り直して実行してみると...

komamitsu
18

と表示され、Ageの(set|get)Age()が使えているのが分かります。RubyのModuleと異なり、Mixin用のクラスは継承可能なので、もしかしたら何か嬉しいかも知れません。

Springを使ってみるメモ

簡単なサンプルを動かそうとするだけで結構はまったのでメモ。


前提とか事前準備:


手順:

  • Help -> Install New Software
  • spring-framework-2.5.6.SEC01-with-dependenciesをプロジェクト化
    • Eclipse上でアクセスしやすいので
    • "New -> Java Project" から "Create project from existing source" で事前にどっかに展開しておいた spring-framework-2.5.6.SEC01-with-dependencies を選択
    • # 鬱陶しいので "Build automatically" は Off にしている
  • Serverの追加
    • "New -> Other -> Server" から適当なもの(今回はTomcat6)を
    • SpringSource dm Server とかも使える?
  • 新規プロジェクト(非Webアプリの場合)
    • "New -> Project -> Spring Project"
    • libとか適当にディレクトリを作って、spring-frameworkプロジェクトからspring.jar, commons-logging.jarをコピって置く. そして "Build Path -> Add JARs" とかで追加しておく
    • CLASSPATHの通ったところに "New -> Other -> Spring Bean Configuration File" を追加して、必要に応じて編集
    • Beanを取り出すコードはこんな感じ
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.*;

  :

  Resource res = new ClassPathResource("applicationContext.xml");
  BeanFactory factory = new XmlBeanFactory(res);
  User komamitsu = (User)factory.getBean("komamitsu");
  • 新規プロジェクト(Webアプリの場合)
    • "New -> Project -> Dynamic Web Project"
    • 追加されたProjectを右クリックして "Spring Tools -> Add Spring Project Nature"
    • /WebContent/WEB-INF/libに、spring-frameworkプロジェクトからspring.jar, commons-logging.jarをコピって置く
    • /WEB-INF/applicationContext.xmlにそれらしくDI設定を書く
    • web.xmlに以下を追記
  <context-param>
  	<param-name>contextConfigLocation</param-name>
  	<param-value>/WEB-INF/applicationContext.xml</param-value>
  </context-param>
  <listener>
  	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
    • ServletからBeanを取り出す場合は以下のような感じで
  WebApplicationContext wac =
    WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext());
  User komamitsu = (User)wac.getBean("komamitsu");