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

如果这个不确定表格也需要我们的war来创建,如何实现。create table的原生SQL,entityManager是无法执行的,因为这不是可以回滚的事务。这种情况,我们需要:

  1. 捕获表格不存在的异常
  2. 从原始的Connection中实现表格创建。

获取Connection

能否从EntityManage中获取Connection依赖于JPA的具体实现,Eclipse的是支持,但是Hibernate不支持。
//可以通过unwrap来获取,可惜Hibernate不支持
Connection conn = entityManager.unwrap(Connection.class);
//如果我们需要使用Hibernate私有的api,可以利用unwrap()获取Hibernate的session
Session session = entityManager.unwrap(Session.class);

此路不通,我们需要从DataSource中获取。

在需要创建表格时抛出异常

public class WantCreateTableException extends RuntimeException{

	private static final long serialVersionUID = 1L;

	public WantCreateTableException(String message) {
		super(message);
	}
}

修改仓库的代码

@Repository
@Validated
public class EventRepository {
	private static final Logger log = LogManager.getLogger();
	private static final Gson gson = new Gson();
	@PersistenceContext private EntityManager entityManager;
	@Inject private DataSource dataSource;
	
	private static final String CREATE_TABLE_SQLFORMAT = "CREATE TABLE IF NOT EXISTS `%s`("
			+ "`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,"
			+ "`data` text,"
			+  "PRIMARY KEY (`id`)"
    		+ ")ENGINE=InnoDB DEFAULT CHARSET=utf8";
	public void createTable(String tableName){
		String sql = String.format(CREATE_TABLE_SQLFORMAT, tableName);
		try(Connection conn = dataSource.getConnection();
				PreparedStatement ps = conn.prepareStatement(sql);){
			ps.execute();
		}catch(SQLException e){
			log.error("(SQL ERROR) Try to create table {} error : {}", tableName, e.toString());
		}	
	}	

	private String getTableName(){
		... ...
	}
	
	public EventData findOne(Long id) {
		log.traceEntry();
		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){
			return null;
		}
	}

	public void save(EventData event) {
		try{
			if(event.getId() == null || event.getId() == 0)
				this.insert(event);
			else
				this.update(event);		
		}catch(Exception e){
			if(isTableNotExist(e, getTableName()))
				throw new WantCreateTableException(getTableName());
			else
				throw e;
		}
	}	

	private boolean update(EventData data) {
               ... ...	
	}
	
	private void insert(EventData data) {
		... ...
	}

	private boolean isTableNotExist(Exception e,String tableName){
		if(e.getCause() == null || e.getCause().getCause() == null)
			return false;
		String message = e.getCause().getCause().getMessage();
		return StringUtils.contains(message, tableName.concat("' doesn't exist"));
	}
	
}

使用例子

@Service
public class TestService {
	@Transactional
	public void test2(){
		EventData event = new EventData();
		event.addData("Hello,world!");
		this.eventRepository.save(event);
	}
}

下面只是测试例子,我们在controller中调用了仓库,这样做实际不好,controller应只调用Service,我们可以在中间有一个业务逻辑service,其调用TestService来完成相关的处理。@Transactional将在遇到RuntimeException是退出事务,而事务是采用proxy的方式,即本类内部调用是不起作用的。

@Controller
public class TestController {
	@Inject TestPageService testService;
	@Inject EventRepository eventRepository;
	private void mytest(){
		try{
			testService.test2();
		}catch(WantCreateTableException e){
			this.eventRepository.createTable(e.getMessage());
			testService.test2();
		}
	}
}

我尝试如下写,但失败了。

//这个没有作用,仍然会报告Transaction was marked for rollback only; cannot commit;
//我的理解是WantCreateTableException只是我封装后的异常,还有本源的异常,已经在hibernate底层触发了rollback:
//严重: Servlet.service() for servlet [springWebDispatcher] in context with path [/chapter22] threw exception 
// [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: 
// Transaction was marked for rollback only; cannot commit; nested exception is org.hibernate.TransactionException: 
// Transaction was marked for rollback only; cannot commit] with root cause
@Transactional(noRollbackFor = WantCreateTableException.class) 
public void test2(){
	EventData event = new EventData();
	event.addData("Hello,world!");		
	try{
		this.eventRepository.save(event);
	}catch(WantCreateTableException e){
		this.eventRepository.createTable(e.getMessage());
		this.eventRepository.save(event);
	}
}
相关链接: 我的Professional Java for Web Applications相关文章


你可能感兴趣的:(JAVA,读书笔记,愷风(Wei)之Java,for,Web学习笔记)