1. 程式人生 > >Java for Web學習筆記(一一一):再談Entity對映(4)動態表格建立

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相關文章