Commit 7c1f5587 authored by 楊慶堂's avatar 楊慶堂

Spring JPA 處理多語系

parent c65cebeb
......@@ -3,13 +3,16 @@ package org.ylhealth.ym.springtest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.ylhealth.ym.springtest.hibernate.I18NRepositoryFactoryBean;
@SpringBootApplication
@EnableTransactionManagement(mode=AdviceMode.ASPECTJ)
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@EnableJpaRepositories(repositoryFactoryBeanClass = I18NRepositoryFactoryBean.class)
public class SpringTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringTestApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(SpringTestApplication.class, args);
}
}
package org.ylhealth.ym.springtest.controller;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.ylhealth.ym.springtest.entity.CrmSystemL1basic;
......@@ -14,4 +17,17 @@ public class I18NTestController {
public CrmSystemL1basic test1() {
return dao.findOne("01");
}
@GetMapping("/i18n/Test11")
public List<CrmSystemL1basic> test11() {
return dao.findAll();
}
@GetMapping("/i18n/Test2")
public CrmSystemL1basic test2() {
LocaleContextHolder.setLocale(Locale.SIMPLIFIED_CHINESE);
return dao.findOne("01");
}
@GetMapping("/i18n/Test3")
public List<CrmSystemL1basic> test3() {
return dao.getAllCustom("01");
}
}
......@@ -2,6 +2,7 @@ package org.ylhealth.ym.springtest.entity;
// Generated Jul 20, 2016 5:22:20 PM by Hibernate Tools 3.2.2.GA
import java.util.Date;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
......@@ -12,17 +13,22 @@ import javax.persistence.TemporalType;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Nationalized;
import org.ylhealth.ym.springtest.hibernate.I18NTranslate;
import org.ylhealth.ym.springtest.hibernate.I18NTranslates;
import com.fasterxml.jackson.annotation.JsonIgnore;
/** CrmSystemL1basic generated by hbm2java */
@Entity
@Table(name = "CRM_SystemL1Basic")
@Cacheable
@I18NTranslates(
id = "groupCode",
table = "CRM_SystemL1Basic_translation",
mapping = {@I18NTranslate(field = "groupName", column = "groupName")})
public class CrmSystemL1basic implements java.io.Serializable {
private String groupCode;
private String func;
@I18NTranslate(column="groupName", id="groupCode", table = "CRM_SystemL1Basic_translation")
private String groupName;
private Character codeStatus;
private String status;
......
package org.ylhealth.ym.springtest.entity;
// Generated Jul 20, 2016 5:22:20 PM by Hibernate Tools 3.2.2.GA
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SecondaryTable;
import javax.persistence.SecondaryTables;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Nationalized;
import com.fasterxml.jackson.annotation.JsonIgnore;
/** CrmSystemL1basic generated by hbm2java */
@Entity
@Table(name = "CRM_SystemL1Basic")
@SecondaryTables(
value = {
@SecondaryTable(
name = "CRM_SystemL1Basic_translation",
pkJoinColumns = {@PrimaryKeyJoinColumn(name = "GroupCode", referencedColumnName = "GroupCode")})
})
public class CrmSystemL1basic2 implements java.io.Serializable {
private String groupCode;
private String func;
private String groupName;
private Character codeStatus;
private String status;
@JsonIgnore private String modifiedId;
@JsonIgnore private Date modifiedTime;
private Integer listSeq;
private String groupName1;
public CrmSystemL1basic2() {}
public CrmSystemL1basic2(
String groupCode,
String func,
String groupName,
String status,
String modifiedId,
Date modifiedTime) {
this.groupCode = groupCode;
this.func = func;
this.groupName = groupName;
this.status = status;
this.modifiedId = modifiedId;
this.modifiedTime = modifiedTime;
}
public CrmSystemL1basic2(
String groupCode,
String func,
String groupName,
Character codeStatus,
String status,
String modifiedId,
Date modifiedTime,
Integer listSeq) {
this.groupCode = groupCode;
this.func = func;
this.groupName = groupName;
this.codeStatus = codeStatus;
this.status = status;
this.modifiedId = modifiedId;
this.modifiedTime = modifiedTime;
this.listSeq = listSeq;
}
@Id
@GenericGenerator(name = "increment", strategy = "uuid2")
@GeneratedValue(generator = "increment")
@Column(name = "GroupCode", unique = true, nullable = false, length = 20)
public String getGroupCode() {
return this.groupCode;
}
public void setGroupCode(String groupCode) {
this.groupCode = groupCode;
}
@Column(name = "Func", nullable = false, length = 20)
public String getFunc() {
return this.func;
}
public void setFunc(String func) {
this.func = func;
}
@Nationalized
@Column(name = "GroupName", nullable = false, length = 50)
public String getGroupName() {
return this.groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
@Column(name = "CodeStatus", length = 1)
public Character getCodeStatus() {
return this.codeStatus;
}
public void setCodeStatus(Character codeStatus) {
this.codeStatus = codeStatus;
}
@Column(name = "Status", nullable = false, length = 1)
public String getStatus() {
return this.status;
}
public void setStatus(String status) {
this.status = status;
}
@Column(name = "ModifiedId", nullable = false, length = 20)
public String getModifiedId() {
return this.modifiedId;
}
public void setModifiedId(String modifiedId) {
this.modifiedId = modifiedId;
}
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "ModifiedTime", nullable = false, length = 23)
public Date getModifiedTime() {
return this.modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
@Column(name = "ListSeq")
public Integer getListSeq() {
return this.listSeq;
}
public void setListSeq(Integer listSeq) {
this.listSeq = listSeq;
}
@Column(name = "GroupName", table = "CRM_SystemL1Basic_translation")
@ColumnTransformer(
read =
"IsNull(CRM_SystemL1Basic_translation.GroupName, CRM_SystemL1Basic.GroupName)")
public String getGroupName1() {
return groupName1;
}
public void setGroupName1(String groupName1) {
this.groupName1 = groupName1;
}
}
......@@ -14,6 +14,7 @@ public class UserInfo implements java.io.Serializable {
private String id;
private String name;
private String cname;
public UserInfo() {}
......
package org.ylhealth.ym.springtest.hibernate;
import org.hibernate.boot.Metadata;
import org.hibernate.cfg.beanvalidation.DuplicationStrategyImpl;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 運用 org.hibernate.integrator.spi.Integrator 處理多國語系基本檔
* 參考:
* https://github.com/deathman92/localized
*/
public class I18NInterceptor implements Integrator {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void integrate(
Metadata metadata,
SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
logger.debug("integrate...");
final EventListenerRegistry eventListenerRegistry =
serviceRegistry.getService(EventListenerRegistry.class);
eventListenerRegistry.addDuplicationStrategy(DuplicationStrategyImpl.INSTANCE);
eventListenerRegistry.appendListeners(
EventType.POST_LOAD, new I18NReadEventListener(sessionFactory));
}
@Override
public void disintegrate(
SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
logger.debug("disintegrate...");
}
}
......@@ -6,82 +6,120 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.Query;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hibernate.SQLQuery;
import org.hibernate.StatelessSession;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.spi.PostLoadEvent;
import org.hibernate.event.spi.PostLoadEventListener;
import org.hibernate.transform.Transformers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.i18n.LocaleContextHolder;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
/** */
public class I18NReadEventListener implements PostLoadEventListener {
/**
*
*/
private static final long serialVersionUID = -7374424944903271183L;
/**
* Spring data 取出後進行多國語系的替換
*
*/
public class I18NPostProcessor implements MethodInterceptor {
private Logger logger = LoggerFactory.getLogger(getClass());
private EntityManager em;
private SessionFactoryImplementor sessionFactory;
@SuppressWarnings("rawtypes")
private Cache<String, List> cache =
CacheBuilder.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES).build();
public I18NReadEventListener(SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory;
public I18NPostProcessor(EntityManager em) {
this.em = em;
}
@SuppressWarnings("rawtypes")
@Override
public void onPostLoad(PostLoadEvent event) {
Object entity = event.getEntity();
Class clazz = entity.getClass();
Collection<Field> fields = getLocalizedFields(clazz);
if (!fields.isEmpty())
try (StatelessSession session =
sessionFactory.openStatelessSession(event.getSession().connection())) {
private Cache<String, Map> cache =
CacheBuilder.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES).build();
Object id = getPKValue(clazz, entity);
String language = LocaleContextHolder.getLocale().toString();
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed();
process(result);
return result;
}
private void process(Object result) throws IllegalAccessException, InvocationTargetException {
if(result instanceof Collection) {
for (Object entity : (Collection)result) {
processEntity(entity);
}
} else {
processEntity(result);
}
}
for (Field field : fields) {
List result = getTranslate(session, id, field, language);
if (!result.isEmpty()) field.set(entity, result.get(0));
private void processEntity(Object entity)
throws IllegalAccessException, InvocationTargetException {
I18NTranslates i18n = entity.getClass().getAnnotation(I18NTranslates.class);
if (i18n != null) {
logger.trace("I18n process: {}", entity);
Class clazz = entity.getClass();
Map<String, String> translate = getTranslate(i18n, entity, clazz);
if (translate != null && !translate.isEmpty()) {
Field[] fields = clazz.getDeclaredFields();
for (I18NTranslate i18nTranslate : i18n.mapping()) {
for (Field field : fields) {
if (field.getName().equals(i18nTranslate.field())) {
field.setAccessible(true);
field.set(entity, translate.get(i18nTranslate.column()));
break;
}
}
}
} catch (Exception e) {
logger.error("ReadEventListener error", e);
throw new IllegalStateException();
}
}
}
private List getTranslate(StatelessSession session, Object id, Field field, String language) {
String key = field + "_" + id + "_" + language;
List result = cache.getIfPresent(key);
/**
*
* @param i18n
* @param entity
* @param clazz
* @return
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
private Map<String, String> getTranslate(I18NTranslates i18n, Object entity, Class clazz)
throws IllegalAccessException, InvocationTargetException {
Object id = getPKValue(clazz, entity);
String language = LocaleContextHolder.getLocale().toString();
String table = i18n.table();
String idColumn = i18n.id();
String key = table + "_" + id + "_" + language;
Map<String, String> result = cache.getIfPresent(key);
if (result == null) {
I18NTranslate localized = field.getAnnotation(I18NTranslate.class);
logger.trace("translate query, table: {}", table);
String sql =
String.format(
"select %s from %s where %s = ? and languageCode = ?",
localized.column(), localized.table(), localized.id());
SQLQuery query = session.createSQLQuery(sql);
query.setParameter(0, id);
query.setParameter(1, language);
result = query.list();
logger.info("SQL: {}: {}", sql, result);
cache.put(key, result);
String.format("select * from %s where %s = ? and languageCode = ?", table, idColumn);
Query query = em.createNativeQuery(sql);
SQLQuery query1 = query.unwrap(org.hibernate.SQLQuery.class);
query1.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
for (I18NTranslate i18nTranslate : i18n.mapping()) {
query1.addScalar(i18nTranslate.column());
}
query.setParameter(1, id);
query.setParameter(2, language);
List<Map<String, String>> result1 = query.getResultList();
if (!result1.isEmpty()) {
result = result1.get(0);
cache.put(key, result);
} else
cache.put(key, new HashMap<>());
}
return result;
}
public static Object getPKValue(Class<?> clazz, Object entity)
private Object getPKValue(Class<?> clazz, Object entity)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Collection<Field> fields = getAllDeclaredFields(clazz);
for (Field field : fields) {
......@@ -97,26 +135,8 @@ public class I18NReadEventListener implements PostLoadEventListener {
}
return null;
}
/**
* Returns the entity's @{@link I18NTranslate} fields.
*
* <p>These fields are made accessible.
*/
private static Collection<Field> getLocalizedFields(Class<?> clazz) {
Collection<Field> fields = getAllDeclaredFields(clazz);
List<Field> localizedFields = new ArrayList<>();
fields
.stream()
.filter(field -> field.getAnnotation(I18NTranslate.class) != null)
.forEach(
field -> {
field.setAccessible(true);
localizedFields.add(field);
});
return localizedFields;
}
private static Collection<Field> getAllDeclaredFields(Class<?> clazz) {
private Collection<Field> getAllDeclaredFields(Class<?> clazz) {
List<Field> fields = new ArrayList<>();
Collections.addAll(fields, clazz.getDeclaredFields());
if (clazz.getSuperclass() != null) {
......
package org.ylhealth.ym.springtest.hibernate;
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor;
public class I18NRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
extends JpaRepositoryFactoryBean<R, T, I> {
public I18NRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
super(repositoryInterface);
}
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
RepositoryFactorySupport factory = super.createRepositoryFactory(em);
factory.addRepositoryProxyPostProcessor(
new RepositoryProxyPostProcessor() {
@Override
public void postProcess(
ProxyFactory factory, RepositoryInformation repositoryInformation) {
factory.addAdvice(new I18NPostProcessor(em));
}
});
return factory;
}
}
......@@ -9,26 +9,17 @@ import java.lang.annotation.Target;
* 利用 annotation 處理多國語系轉換
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Target({ElementType.TYPE})
public @interface I18NTranslate {
/**
* 對應的欄位
* class 的欄位
* @return
*/
String column() default "";
String field();
/**
* 對應的 id
* @return
*/
String id() default "";
/**
* 翻譯的表格
* 對應的欄位
* @return
*/
String table() default "";
String column() default "";
}
package org.ylhealth.ym.springtest.hibernate;
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 I18NTranslates {
/**
* 對應的 id
*
* @return
*/
String id() default "";
/**
* 翻譯的表格
*
* @return
*/
String table() default "";
/**
* 資料對應
*/
I18NTranslate[] mapping();
}
......@@ -3,4 +3,5 @@ package org.ylhealth.ym.springtest.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.ylhealth.ym.springtest.entity.CrmSystemL1basic;
public interface CrmSystemL1basicRepo extends JpaRepository<CrmSystemL1basic, String> {}
public interface CrmSystemL1basicRepo
extends JpaRepository<CrmSystemL1basic, String>, CrmSystemL1basicRepoCustom {}
package org.ylhealth.ym.springtest.repo;
import java.util.List;
import org.ylhealth.ym.springtest.entity.CrmSystemL1basic;
public interface CrmSystemL1basicRepoCustom {
public List<CrmSystemL1basic> getAllCustom(String id);
}
package org.ylhealth.ym.springtest.repo;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.ylhealth.ym.springtest.entity.CrmSystemL1basic;
public class CrmSystemL1basicRepoImpl implements CrmSystemL1basicRepoCustom {
@PersistenceContext private EntityManager em;
@Override
public List<CrmSystemL1basic> getAllCustom(String id) {
List<CrmSystemL1basic> data =
em.createQuery("from CrmSystemL1basic where groupCode = :groupCode")
.setParameter("groupCode", id)
.setHint("org.hibernate.cacheable", "true")
.getResultList();
return data;
}
}
org.ylhealth.ym.springtest.hibernate.I18NInterceptor
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment