Java for Web學習筆記(一一一):再談Entity對映(4)動態表格建立
阿新 • • 發佈:2019-02-11
如果這個不確定表格也需要我們的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相關文章