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

Spring JPA 處理多語系

parent c65cebeb
...@@ -3,10 +3,13 @@ package org.ylhealth.ym.springtest; ...@@ -3,10 +3,13 @@ package org.ylhealth.ym.springtest;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.AdviceMode;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.ylhealth.ym.springtest.hibernate.I18NRepositoryFactoryBean;
@SpringBootApplication @SpringBootApplication
@EnableTransactionManagement(mode=AdviceMode.ASPECTJ) @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@EnableJpaRepositories(repositoryFactoryBeanClass = I18NRepositoryFactoryBean.class)
public class SpringTestApplication { public class SpringTestApplication {
public static void main(String[] args) { public static void main(String[] args) {
......
package org.ylhealth.ym.springtest.controller; package org.ylhealth.ym.springtest.controller;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.ylhealth.ym.springtest.entity.CrmSystemL1basic; import org.ylhealth.ym.springtest.entity.CrmSystemL1basic;
...@@ -14,4 +17,17 @@ public class I18NTestController { ...@@ -14,4 +17,17 @@ public class I18NTestController {
public CrmSystemL1basic test1() { public CrmSystemL1basic test1() {
return dao.findOne("01"); 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; ...@@ -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 // Generated Jul 20, 2016 5:22:20 PM by Hibernate Tools 3.2.2.GA
import java.util.Date; import java.util.Date;
import javax.persistence.Cacheable;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
...@@ -12,17 +13,22 @@ import javax.persistence.TemporalType; ...@@ -12,17 +13,22 @@ import javax.persistence.TemporalType;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Nationalized; import org.hibernate.annotations.Nationalized;
import org.ylhealth.ym.springtest.hibernate.I18NTranslate; import org.ylhealth.ym.springtest.hibernate.I18NTranslate;
import org.ylhealth.ym.springtest.hibernate.I18NTranslates;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
/** CrmSystemL1basic generated by hbm2java */ /** CrmSystemL1basic generated by hbm2java */
@Entity @Entity
@Table(name = "CRM_SystemL1Basic") @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 { public class CrmSystemL1basic implements java.io.Serializable {
private String groupCode; private String groupCode;
private String func; private String func;
@I18NTranslate(column="groupName", id="groupCode", table = "CRM_SystemL1Basic_translation")
private String groupName; private String groupName;
private Character codeStatus; private Character codeStatus;
private String status; 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 { ...@@ -14,6 +14,7 @@ public class UserInfo implements java.io.Serializable {
private String id; private String id;
private String name; private String name;
private String cname; private String cname;
public UserInfo() {} 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; ...@@ -6,82 +6,120 @@ import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.persistence.EntityManager;
import javax.persistence.Id; 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.SQLQuery;
import org.hibernate.StatelessSession; import org.hibernate.transform.Transformers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.spi.PostLoadEvent;
import org.hibernate.event.spi.PostLoadEventListener;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
/** */ /**
public class I18NReadEventListener implements PostLoadEventListener { * Spring data 取出後進行多國語系的替換
/**
* *
*/ */
private static final long serialVersionUID = -7374424944903271183L; public class I18NPostProcessor implements MethodInterceptor {
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
private EntityManager em;
private SessionFactoryImplementor sessionFactory; public I18NPostProcessor(EntityManager em) {
@SuppressWarnings("rawtypes") this.em = em;
private Cache<String, List> cache =
CacheBuilder.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES).build();
public I18NReadEventListener(SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory;
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override private Cache<String, Map> cache =
public void onPostLoad(PostLoadEvent event) { CacheBuilder.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES).build();
Object entity = event.getEntity();
Class clazz = entity.getClass();
Collection<Field> fields = getLocalizedFields(clazz);
if (!fields.isEmpty())
try (StatelessSession session =
sessionFactory.openStatelessSession(event.getSession().connection())) {
Object id = getPKValue(clazz, entity); @Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed();
process(result);
return result;
}
String language = LocaleContextHolder.getLocale().toString(); private void process(Object result) throws IllegalAccessException, InvocationTargetException {
if(result instanceof Collection) {
for (Object entity : (Collection)result) {
processEntity(entity);
}
} else {
processEntity(result);
}
}
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) { for (Field field : fields) {
List result = getTranslate(session, id, field, language); if (field.getName().equals(i18nTranslate.field())) {
if (!result.isEmpty()) field.set(entity, result.get(0)); field.setAccessible(true);
field.set(entity, translate.get(i18nTranslate.column()));
break;
} }
} catch (Exception e) {
logger.error("ReadEventListener error", e);
throw new IllegalStateException();
} }
} }
}
}
}
/**
*
* @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();
private List getTranslate(StatelessSession session, Object id, Field field, String language) { String key = table + "_" + id + "_" + language;
String key = field + "_" + id + "_" + language; Map<String, String> result = cache.getIfPresent(key);
List result = cache.getIfPresent(key);
if (result == null) { if (result == null) {
I18NTranslate localized = field.getAnnotation(I18NTranslate.class); logger.trace("translate query, table: {}", table);
String sql = String sql =
String.format( String.format("select * from %s where %s = ? and languageCode = ?", table, idColumn);
"select %s from %s where %s = ? and languageCode = ?", Query query = em.createNativeQuery(sql);
localized.column(), localized.table(), localized.id()); SQLQuery query1 = query.unwrap(org.hibernate.SQLQuery.class);
SQLQuery query = session.createSQLQuery(sql); query1.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
query.setParameter(0, id); for (I18NTranslate i18nTranslate : i18n.mapping()) {
query.setParameter(1, language); query1.addScalar(i18nTranslate.column());
result = query.list(); }
logger.info("SQL: {}: {}", sql, result); 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); cache.put(key, result);
} else
cache.put(key, new HashMap<>());
} }
return result; return result;
} }
public static Object getPKValue(Class<?> clazz, Object entity) private Object getPKValue(Class<?> clazz, Object entity)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Collection<Field> fields = getAllDeclaredFields(clazz); Collection<Field> fields = getAllDeclaredFields(clazz);
for (Field field : fields) { for (Field field : fields) {
...@@ -97,26 +135,8 @@ public class I18NReadEventListener implements PostLoadEventListener { ...@@ -97,26 +135,8 @@ public class I18NReadEventListener implements PostLoadEventListener {
} }
return null; 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<>(); List<Field> fields = new ArrayList<>();
Collections.addAll(fields, clazz.getDeclaredFields()); Collections.addAll(fields, clazz.getDeclaredFields());
if (clazz.getSuperclass() != null) { 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; ...@@ -9,26 +9,17 @@ import java.lang.annotation.Target;
* 利用 annotation 處理多國語系轉換 * 利用 annotation 處理多國語系轉換
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD}) @Target({ElementType.TYPE})
public @interface I18NTranslate { public @interface I18NTranslate {
/** /**
* 對應的欄位 * class 的欄位
* @return * @return
*/ */
String column() default ""; String field();
/** /**
* 對應的 id * 對應的欄位
* @return
*/
String id() default "";
/**
* 翻譯的表格
* @return * @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; ...@@ -3,4 +3,5 @@ package org.ylhealth.ym.springtest.repo;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.ylhealth.ym.springtest.entity.CrmSystemL1basic; 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