Skip to content

Instantly share code, notes, and snippets.

@orekyuu
Created October 11, 2015 12:07
Show Gist options
  • Save orekyuu/2f72749a408affb41342 to your computer and use it in GitHub Desktop.
Save orekyuu/2f72749a408affb41342 to your computer and use it in GitHub Desktop.
package app;
import app.entity.User;
import lib.SqlWrapper;
import java.util.List;
public class App {
private static final String url = "jdbc:derby://localhost:1527/test";
public static void main(String[] args) {
SqlWrapper wrapper = new SqlWrapper(url);
UserRepository userRepository = wrapper.createRepository(UserRepository.class);
List<User> all = userRepository.findAll();
for (User user : all) {
System.out.println(user);
}
System.out.println("findByAge");
for (User user : userRepository.findUsersByAge(20)) {
System.out.println(user);
}
}
}
package app.entity;
import lib.annotations.Column;
import lib.annotations.Table;
@Table("USER_TABLE")
public class User {
@Column("ID")
private long id;
@Column("USER_NAME")
private String name;
@Column("AGE")
private long age;
public long getId() {
return id;
}
public String getName() {
return name;
}
public long getAge() {
return age;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("User{");
sb.append("id=").append(id);
sb.append(", name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
}
package app;
import app.entity.User;
import lib.Repository;
import lib.annotations.Parameter;
import lib.annotations.Select;
import java.util.List;
import java.util.Optional;
public interface UserRepository extends Repository<User> {
@Select("WHERE USER_NAME = ':name'")
Optional<User> findByName(@Parameter("name") String name);
@Select("WHERE AGE = :age")
List<User> findUsersByAge(@Parameter("age") int age);
}
package lib.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
/**
* @return カラムの名前
*/
String value();
}
package lib.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Delete {
String value() default "";
}
package lib.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Parameter {
String value();
}
package lib.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
String value() default "";
}
package lib.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
/**
* @return テーブルの名前
*/
String value();
}
package lib.processor;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public interface ResultSetFunction {
List<Object> accept(ResultSet resultSet) throws SQLException;
}
package lib.processor;
import lib.annotations.Column;
import lib.annotations.Select;
import lib.annotations.Table;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
public class SelectMethodProcessor extends SqlProcessorBase {
private final Select select;
public SelectMethodProcessor(String jdbcUrl, Type type, Select select) {
super(jdbcUrl, type);
this.select = select;
}
@Override
protected Object doProcess(Object proxy, Method method, Object[] args) {
String sql = createSql(method, args);
List<Object> result = getResult(sql);
//戻り値の型に合わせる
Class<?> returnType = method.getReturnType();
if (returnType.isAssignableFrom(Optional.class)) {
return result.isEmpty() ? Optional.empty() : Optional.ofNullable(result.get(0));
}
if (returnType.isAssignableFrom(List.class)) {
return result;
}
return result.isEmpty() ? null : result.get(0);
}
private String createSql(Method method, Object[] args) {
Table table = getEntityClass().getAnnotation(Table.class);
Objects.requireNonNull(table, "Tableアノテーションが見つかりません");
StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM " + table.value());
String sql = select.value();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
lib.annotations.Parameter annotation = parameters[i].getAnnotation(lib.annotations.Parameter.class);
Objects.requireNonNull(annotation, "Parameterアノテーションが見つかりません");
sql = sql.replace(":" + annotation.value(), args[i].toString());
}
return sqlBuilder.append(" ").append(sql).toString();
}
private List<Object> getResult(String sql) {
return executeQuery(sql, resultSet -> {
Class<?> entityClass = getEntityClass();
ArrayList<Object> list = new ArrayList<>();
while (resultSet.next()) {
try {
//結果を入れるEntityを作成
Object entityInstance = entityClass.getConstructor().newInstance();
list.add(entityInstance);
//結果をフィールドに詰める
for (Field field : entityClass.getDeclaredFields()) {
Column column = field.getAnnotation(Column.class);
if (column == null) {
continue;
}
int index = resultSet.findColumn(column.value());
Object object = resultSet.getObject(index);
field.setAccessible(true);
//フィールドがOptionalならOptionalに入れる
if (field.getType().isAssignableFrom(Optional.class)) {
field.set(entityInstance, Optional.ofNullable(object));
} else {
field.set(entityInstance, object);
}
}
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
return list;
});
}
}
package lib.processor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public abstract class SqlProcessorBase {
private Class<?> entityClass;
private final String jdbcUrl;
public SqlProcessorBase(String jdbcUrl, Type type) {
this.jdbcUrl = jdbcUrl;
try {
this.entityClass = Class.forName(type.getTypeName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public Class<?> getEntityClass() {
return entityClass;
}
public final Object process(Object proxy, Method method, Object[] args) {
return doProcess(proxy, method, args);
}
protected abstract Object doProcess(Object proxy, Method method, Object[] args);
protected final List<Object> executeQuery(String sql, ResultSetFunction consumer) {
System.out.println("executeQuery: " + sql);
try (Connection connection = DriverManager.getConnection(jdbcUrl);
Statement statement = connection.createStatement()) {
ResultSet resultSet = statement.executeQuery(sql);
return consumer.accept(resultSet);
} catch (SQLException e) {
e.printStackTrace();
}
return new ArrayList<>();
}
}
package lib.processor;
public class ValidateException extends RuntimeException {
public ValidateException(String message) {
super(message);
}
}
package lib;
import lib.annotations.Select;
import java.util.List;
public interface Repository<ENTITY> {
@Select
List<ENTITY> findAll();
}
package lib;
import java.lang.reflect.Proxy;
import java.util.Objects;
public class SqlWrapper {
private final String jdbcUrl;
public SqlWrapper(String jdbcUrl) {
Objects.requireNonNull(jdbcUrl);
this.jdbcUrl = jdbcUrl;
}
@SuppressWarnings("unchecked")
public <T extends Repository> T createRepository(Class<T> repositoryInterface) {
return (T) Proxy.newProxyInstance(
repositoryInterface.getClassLoader(),
new Class[]{repositoryInterface},
new SqlWrapperInvocationHandler(jdbcUrl, repositoryInterface));
}
}
package lib;
import lib.annotations.Select;
import lib.processor.SelectMethodProcessor;
import lib.processor.SqlProcessorBase;
import lib.processor.ValidateException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Objects;
class SqlWrapperInvocationHandler implements InvocationHandler {
private final String jdbcUrl;
private final Class proxyInterface;
private Type entityType;
SqlWrapperInvocationHandler(String jdbcUrl, Class proxyInterface) {
this.jdbcUrl = jdbcUrl;
this.proxyInterface = proxyInterface;
getEntityType(proxyInterface);
if (entityType instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) this.entityType;
entityType = type.getActualTypeArguments()[0];
}
Objects.requireNonNull(entityType, "Entityが見つけられませんでした");
}
@SuppressWarnings("unchecked")
private Class<Repository> getEntityType(Class<?> clazz) {
for (Class<?> a : clazz.getInterfaces()) {
if (a == Repository.class) {
entityType = clazz.getGenericInterfaces()[0];
return (Class<Repository>) a;
} else {
Class<Repository> repositoryClass = getEntityType(a);
if (repositoryClass != null) {
entityType = a.getGenericInterfaces()[0];
return repositoryClass;
}
}
}
return null;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlProcessorBase processor = null;
Select select = method.getAnnotation(Select.class);
if (select != null) {
processor = new SelectMethodProcessor(jdbcUrl, entityType, select);
}
if (processor == null) {
throw new ValidateException(proxy.getClass().getName() + "#" + method.getName() + "に対応するProcessorが見つかりません。");
}
return processor.process(proxy, method, args);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment