Java for Web学习笔记(一一零):再谈Entity映射(3)动态表格

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

猜你喜欢

转载自blog.csdn.net/flowingflying/article/details/79358282