如果这个不确定表格也需要我们的war来创建,如何实现。create table的原生SQL,entityManager是无法执行的,因为这不是可以回滚的事务。这种情况,我们需要:
- 捕获表格不存在的异常
- 从原始的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相关文章