在某些场景中,需要使用动态表格,例如表格名字为abc_f(x),其中f(x)为变化后缀,可能是基于时间,也可能是基于用户名,基于ip地址或者别的什么计算出来的。
小例子准备
表为Event_<年>,data里面存放一个json的复杂结构List<String>,而这种复杂结构将对代码产生影响,后面详细讨论。
CREATE TABLE `Event_2018` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`data` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
映射的Entity
虽然无法实现Entity和表格的一一映射,但是我们仍需要一个Entity来对应表格中的列。默认地,EventEntity将对应表格EventEntity,该表并不实际存在。
@Entity
public class EventEntity {
private static final Gson gson = new Gson();
private Long id;
private List<String> data;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Convert(converter = StringListConverter.class)
public List<String> getData() {
return data;
}
public void setData(List<String> data) {
this.data = data;
}
@Transient
public void addData(String event){
if(this.data == null)
this.data = new Vector<>();
this.data.add(event);
}
public static class StringListConverter implements AttributeConverter<List<String>, String> {
private static final Type STRING_TYPE = new TypeToken<Vector<String>>() {}.getType();
@Override
public String convertToDatabaseColumn(List<String> attribute) {
if (attribute == null)
return null;
else
return gson.toJson(attribute);
}
@Override
public List<String> convertToEntityAttribute(String dbData) {
if (dbData == null)
return null;
else
return gson.fromJson(dbData, STRING_TYPE);
}
}
}
仓库的实现
//1、我们将学习如果在entityManager中通过createNativeQuery()使用原生的SQL,来解决表格对应的问题
//2、在原生SQL方式中,如何获取自动分配的id。
@Repository
public class EventRepository {
private static final Gson gson = new Gson();
@PersistenceContext private EntityManager entityManager;
private String getTableName(){
return "Event_" + LocalDateTime.now().getYear();
}
public EventEntity findOne(Long id) {
String tableName = getTableName();
try{
String sql = String.format("SELECT * FROM `%s` WHERE `id`=?", tableName,id);
//如果使用.getSingleResult(),当不存在时,会出现NoResultException,我们需要进行获取。或者我们获取List<EventEntity>,如果为空,返回null,非空,则返回第一个元素
return (EventEntity) entityManager.createNativeQuery(sql,EventEntity.class)
.setParameter(1, id).getSingleResult();
}catch(Exception e){
if(e instanceof NoResultException)
return null;
throw e;
}
}
public void save(EventEntity entity) {
if(entity.getId() == null || entity.getId() == 0)
this.insert(entity);
else
this.update(entity);
}
private boolean update(EventEntity data) {
String tableName = getTableName();
String sql = String.format("UPDATE `%s` SET `data`=? WHERE `id`=?", tableName);
return this.entityManager.createNativeQuery(sql)
.setParameter(1, gson.toJson(data.getData()))
.setParameter(2, data.getId())
.executeUpdate() > 0;
}
private void insert(EventEntity data) {
String sql = String.format("INSERT INTO `%s`(`data`) VALUES(?)", getTableName());
this.entityManager.createNativeQuery(sql)
.setParameter(1, gson.toJson(data.getData()))
.executeUpdate();
sql = "SELECT LAST_INSERT_ID()";
BigInteger id = (BigInteger)this.entityManager.createNativeQuery(sql).getSingleResult();
data.setId(id.longValue());
}
}
存在的问题
上面的小例子看起来一切正常,但是如果我们调用下面的测试方法,就会出现问题。
/* 如果只读是不需要放在事务当中的,我查了很久,定位了这段代码的错误,单独拿出来进行测试。*/
@Transactional
public void test(long id){
EventEntity entity = this.eventRepository.findOne(id);
if(entity != null){
entity.addData("Test");
}
}
会出现下面的错误:
Hibernate: SELECT * FROM `Event_2018` WHERE `id`=?
Hibernate: update EventEntity set data=? where id=?
09:35:37.711 [http-nio-8080-exec-2] [WARN ] Hibernate SqlExceptionHelper - SQL Error: 1146, SQLState: 42S02
09:35:37.712 [http-nio-8080-exec-2] [ERROR] Hibernate SqlExceptionHelper - Table 'test.EventEntity' doesn't exist
09:35:37.714 [http-nio-8080-exec-2] [INFO ] Hibernate AbstractBatchImpl - HHH000010: On release of batch it still contained JDBC statements
09:35:37.714 [http-nio-8080-exec-2] [ERROR] Hibernate ExceptionMapperStandardImpl - HHH000346: Error during managed flush [org.hibernate.exception.SQLGrammarException: could not execute statement]
二月 12, 2018 9:35:37 上午 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet [springWebDispatcher] in context with path [/chapter22] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statement] with root cause
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'test.EventEntity' doesn't exist
问题在于是什么触发了update EventEntity set data=? where id=?这个语句。定位在
entity.addData("Test");
也就是说,在事务里面,如果entity的属性发生了变化,无论是对象的变化,还是对象本身出现变化,hibernate在事务中将触发update语句,由于entity并没有对应真实的表格,出现了错误。Hibernate的智能超出想象,但对于原生的事务方式,造成麻烦。
解决这个问题也很简单:我们将构造一个去除复杂结构的EventEntity作为对应,并构造一个具有复杂结构的EventData和其对应。(关键在于定位问题)
代码修定
@Entity
public class EventEntity {
private Long id;
private String data;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Basic
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
public class EventData {
private static final Gson gson = new Gson();
private static final Type LIST_STRING_TYPE = new TypeToken<Vector<String>>() {}.getType();
private Long id;
private List<String> data;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<String> getData() {
return data;
}
public void setData(List<String> data) {
this.data = data;
}
public void addData(String str){
if(this.data == null)
this.data = new Vector<>();
data.add(str);
}
public static EventData build(EventEntity entity){
EventData event = new EventData();
event.setId(entity.getId());
if(entity.getData() != null)
event.setData( gson.fromJson(entity.getData(), LIST_STRING_TYPE));
return event;
}
}
@Repository
@Validated
public class EventRepository {
private static final Gson gson = new Gson();
@PersistenceContext private EntityManager entityManager;
private String getTableName(){
return "Event_" + LocalDateTime.now().getYear();
}
public EventData findOne(@NotNull Long id) {
String tableName = getTableName();
try{
String sql = String.format("SELECT * FROM `%s` WHERE `id`=?", tableName,id);
return EventData.build((EventEntity) entityManager.createNativeQuery(sql,EventEntity.class)
.setParameter(1, id).getSingleResult());
}catch(Exception e){
if(e instanceof NoResultException){
return null;
}else{
throw e;
}
}
}
public void save(EventData event) {
if(event.getId() == null || event.getId() == 0)
this.insert(event);
else
this.update(event);
}
private boolean update(EventData data) {
String tableName = getTableName();
String sql = String.format("UPDATE `%s` SET `data`=? WHERE `id`=?", tableName);
return this.entityManager.createNativeQuery(sql)
.setParameter(1, gson.toJson(data.getData()))
.setParameter(2, data.getId())
.executeUpdate() > 0;
}
private void insert(EventData data) {
String sql = String.format("INSERT INTO `%s`(`data`) VALUES(?)", getTableName());
this.entityManager.createNativeQuery(sql)
.setParameter(1, gson.toJson(data.getData()))
.executeUpdate();
sql = "SELECT LAST_INSERT_ID()";
BigInteger id = (BigInteger)this.entityManager.createNativeQuery(sql).getSingleResult();
data.setId(id.longValue());
}
}
相关链接:
我的Professional Java for Web Applications相关文章