在某些场景中,需要使用动态表格,例如表格名字为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相关文章