package org.hibernate.ogm.repository;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.ogm.grid.EntityKey;
import org.hibernate.redis.jedis.JedisCallback;
import org.hibernate.redis.jedis.JedisTransactionalCallback;
import org.hibernate.redis.serializer.BinaryRedisSerializer;
import org.hibernate.redis.serializer.RedisSerializer;
import org.hibernate.redis.serializer.SerializationTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;
import java.util.*;
/**
* hibernate-ogm 의 엔티티에 대한 CRUD를 담당하는 Repository입니다.
*
* @author 배성혁 sunghyouk.bae@gmail.com
* @since 13. 5. 3. 오후 11:57
*/
@SuppressWarnings( "unchecked" )
public class RedisRepository {
private static final Logger log = LoggerFactory.getLogger(RedisRepository.class);
private static final boolean isTraceEnabled = log.isTraceEnabled();
private static final boolean isDebugEnabled = log.isDebugEnabled();
@Getter
private final JedisPool jedisPool;
@Getter
@Setter
private int database;
@Getter
private static final RedisSerializer fieldSerializer = new BinaryRedisSerializer<Object>();
@Getter
private static final RedisSerializer valueSerializer = new BinaryRedisSerializer<Object>();
public static final String ENTITY_HSET = "OGM-Entity";
public static final byte[] RAW_ENTITY_HSET = fieldSerializer.serialize(ENTITY_HSET);
public static final String ASSOCIATION_HSET = "OGM_Association";
public static final byte[] RAW_ASSOCIATION_HSET = fieldSerializer.serialize(ASSOCIATION_HSET);
public static final String SEQUENCE_HSET = "OGM-Sequence";
public static final byte[] RAW_SEQUENCE_HSET = fieldSerializer.serialize(SEQUENCE_HSET);
public RedisRepository(JedisPool pool) {
this(pool, 0);
}
public RedisRepository(JedisPool pool, int database) {
this.jedisPool = pool;
this.database = database;
}
public Object getEntitySet(EntityKey key) {
if (isTraceEnabled)
log.trace("엔티티 집합을 조회합니다... key=[{}]", key);
final byte[] rawField = fieldSerializer.serialize(key);
byte[] rawValue = run(new JedisCallback<byte[]>() {
@Override
public byte[] execute(Jedis jedis) {
return jedis.hget(RAW_ENTITY_HSET, rawField);
}
});
return valueSerializer.deserialize(rawValue);
}
public void putEntity(EntityKey key, Map<String, Object> tuple) {
if (isTraceEnabled)
log.trace("엔티티를 저장합니다. key=[{}], tuple=[{}]", key, tuple);
final byte[] rawField = fieldSerializer.serialize(key);
final byte[] rawValue = valueSerializer.serialize(tuple);
try {
run(new JedisCallback<Long>() {
@Override
public Long execute(Jedis jedis) {
return jedis.hset(RAW_ENTITY_HSET, rawField, rawValue);
}
});
} catch (Exception e) {
log.error("엔티티 저장에 실패했습니다.", e);
throw new RuntimeException(e);
}
}
public void removeEntity(EntityKey key) {
if (isTraceEnabled)
log.trace("엔티티를 삭제합니다... key=[{}]", key);
final byte[] rawField = fieldSerializer.serialize(key);
Long result = run(new JedisCallback<Long>() {
@Override
public Long execute(Jedis jedis) {
return jedis.hdel(RAW_ENTITY_HSET, rawField);
}
});
}
public byte[] rawField(EntityKey key) {
Map<String, String> map = new HashMap<String, String>();
map.put("columNames", Integer.toString(key.hashCode()));
map.put("table", key.getTable());
return fieldSerializer.serialize(map);
}
/** 키를 byte[] 로 직렬화합니다 * */
@SuppressWarnings( "unchecked" )
private byte[] rawKey(Object key) {
return getFieldSerializer().serialize(key);
}
@SuppressWarnings( "unchecked" )
private byte[][] rawKeys(Collection<? extends Object> keys) {
byte[][] rawKeys = new byte[keys.size()][];
int i = 0;
for (Object key : keys) {
rawKeys[i++] = getFieldSerializer().serialize(key);
}
return rawKeys;
}
/** byte[] 를 key 값으로 역직렬화 합니다 */
private Object deserializeKey(byte[] rawKey) {
return getFieldSerializer().deserialize(rawKey);
}
/** 캐시 값을 byte[]로 직렬화를 수행합니다. */
@SuppressWarnings( "unchecked" )
private byte[] rawValue(Object value) {
return getValueSerializer().serialize(value);
}
/** byte[] 를 역직렬화하여 원 객체로 변환합니다. */
private Object deserializeValue(byte[] rawValue) {
return getValueSerializer().deserialize(rawValue);
}
/**
* Redis 작업을 수행합니다.<br/>
* {@link redis.clients.jedis.JedisPool} 을 이용하여, {@link redis.clients.jedis.Jedis}를 풀링하여 사용하도록 합니다.
*/
private <T> T run(final JedisCallback<T> callback) {
final Jedis jedis = jedisPool.getResource();
try {
if (database != 0) jedis.select(database);
return callback.execute(jedis);
} catch (Throwable t) {
log.error("Redis 작업 중 예외가 발생했습니다.", t);
throw new RuntimeException(t);
} finally {
jedisPool.returnResource(jedis);
}
}
/**
* 복수의 작업을 하나의 Transaction 하에서 수행하도록 합니다.<br />
* {@link redis.clients.jedis.JedisPool} 을 이용하여, {@link redis.clients.jedis.Jedis}를 풀링하여 사용하도록 합니다.
*/
private List<Object> runWithTx(final JedisTransactionalCallback callback) {
final Jedis jedis = jedisPool.getResource();
try {
if (database != 0) jedis.select(database);
Transaction tx = jedis.multi();
callback.execute(tx);
return tx.exec();
} catch (Throwable t) {
log.error("Redis 작업 중 예외가 발생했습니다.", t);
throw new RuntimeException(t);
} finally {
jedisPool.returnResource(jedis);
}
}
/** Raw Key 값들을 역직렬화하여 Key Set을 반환합니다. */
@SuppressWarnings( "unchecked" )
private Set<Object> deserializeKeys(Set<byte[]> rawKeys) {
return SerializationTool.deserialize(rawKeys, getFieldSerializer());
}
/** Raw Value 값들을 역직렬화하여 Value List를 반환합니다. */
@SuppressWarnings( "unchecked" )
private List<Object> deserializeValues(List<byte[]> rawValues) {
return SerializationTool.deserialize(rawValues, getValueSerializer());
}
}