Imite o mecanismo de criação automática de tabelas do fluxo de trabalho do Activiti para realizar a solução de criação automática de bancos de dados e tabelas associadas a várias tabelas após o início do projeto Springboot.

Texto/Zhu Jiqian

Fiquei acordado até tarde para terminar de escrever e ainda há algumas lacunas, mas ainda estou estudando muito e resumindo, seus gostos e atenção são o maior incentivo para mim!

No desenvolvimento de alguns projetos de localização, há uma demanda de que o projeto desenvolvido precise ser capaz de construir sozinho o banco de dados exigido pelo sistema e suas tabelas de banco de dados correspondentes quando a primeira implantação for iniciada.

Para resolver essas necessidades, existem muitas estruturas de código aberto que podem gerar tabelas de banco de dados automaticamente, como mybatis plus, spring JPA, etc., mas você já pensou em construir uma estrutura de tabela mais complexa sozinho? esta estrutura de código aberto também satisfaz os requisitos? Se não, como isso pode ser alcançado?

Eu escrevi uma nota de estudo do fluxo de trabalho do Activiti (3) antes  - uma análise dos princípios subjacentes à geração automática de 28 tabelas de banco de dados . Nele, analisei os princípios subjacentes da construção automática de 28 tabelas de banco de dados do fluxo de trabalho Activiti. Na minha opinião, uma das razões para aprender os princípios subjacentes dos frameworks de código aberto é aprender algo que possa ser usado por mim. Portanto, depois de analisar e compreender os princípios subjacentes do fluxo de trabalho para construir automaticamente 28 tabelas de banco de dados, decidi escrever também uma demonstração de bancos de dados e tabelas autocriados com base na estrutura Springboot. Referi-me à lógica da implementação de criação de tabela subjacente da versão Activiti6.0 do fluxo de trabalho.Com base na estrutura Springboot, o projeto de implementação pode construir automaticamente vários bancos de dados e tabelas complexos, como associações de múltiplas tabelas, quando é iniciado pela primeira vez.

A ideia geral de implementação não é complicada, é mais ou menos assim: primeiro projete um conjunto de scripts sql de banco de dados que criem completamente associações de múltiplas tabelas, coloque-os em recursos e execute automaticamente os scripts sql durante o processo de inicialização do springboot.

Primeiro, vamos projetar um conjunto de scripts viáveis ​​de banco de dados de associação de múltiplas tabelas de uma só vez. Aqui me refiro principalmente ao uso das próprias tabelas do Activiti como um caso de implementação. Como ele projetou muitas associações de tabelas internamente, nenhum design adicional é necessário.

A instrução do script SQL é a instrução comum de criação de tabela, semelhante à seguinte:

  1 criar tabela ACT_PROCDEF_INFO ( 
  2 ID_ varchar(64) não nulo, 
  3 PROC_DEF_ID_ varchar(64) não nulo, 
  4 REV_ inteiro, 
  5 INFO_JSON_ID_ varchar(64), 
  6 chave primária (ID_) 
  7) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin;

Adicione chaves primárias e índices externos——

  1 cria o índice ACT_IDX_INFO_PROCDEF em ACT_PROCDEF_INFO(PROC_DEF_ID_); 
  2 
  3 alterar tabela ACT_PROCDEF_INFO 
  4 adicionar restrição ACT_FK_INFO_JSON_BA 
  5 chave estrangeira (INFO_JSON_ID_) 
  6 referências ACT_GE_BYTEARRAY (ID_); 
  7 
  8 alterar tabela ACT_PROCDEF_INFO 
  9 adicionar restrição ACT_FK_INFO_PROCDEF 
 10 chave estrangeira (PROC_DEF_ID_) 
 11 referências ACT_RE_PROCDEF (ID_); 
 12 
 13 alterar tabela ACT_PROCDEF_INFO 
 14 adicionar restrição ACT_UNIQ_INFO_PROCDEF 
 15 exclusivo (PROC_DEF_ID_);

O objetivo é projetar um conjunto de instruções SQL que atendam aos cenários de demanda, salvá-las no arquivo de script .sql e, finalmente, armazená-las no diretório de recursos, semelhante ao seguinte:

imagem-20210315132805036

A próxima etapa é implementar a interface CommandLineRunner, reescrever seu método de retorno de chamada do bean run() e desenvolver funções que possam construir automaticamente a lógica do banco de dados e da tabela no método run.

No momento, carreguei a demonstração desenvolvida em meu github. As crianças interessadas podem baixá-la sozinhas. Atualmente, ela pode ser baixada diretamente e executada no ambiente local. Pode ser usada como referência de acordo com suas necessidades reais.

Em primeiro lugar, ao resolver tais necessidades, a primeira coisa a resolver é como implementar o método de criação de tabelas apenas uma vez após o início do Springboot.

Aqui você precisa usar uma interface CommandLineRunner, que vem com o Springboot. A classe que implementa essa interface e seu método de execução substituído serão executados automaticamente após o início do Springboot. O código-fonte desta interface é o seguinte:

  1 @FunctionalInterface 
  2 public interface CommandLineRunner { 
  3 
  4 /** 
  5 *Callback 
  6 para execução de beans */ 
  7 void run(String... args) lança Exception; 
  8 
  9 }

Para expandir, no Springboot, você pode definir várias classes que implementam a interface CommandLineRunner e classificar essas classes de implementação. Você só precisa adicionar @Order e seu método de execução substituído pode ser executado em ordem. Verificação de caso de código:

  1 @Component 
  2 @Order(value=1) 
  3 public class WatchStartCommandSqlRunnerImpl implements CommandLineRunner { 
  4 
  5 @Override 
  6 public void run(String... args) lança Exception { 
  7 System.out.println("第一个Command执行" ); 
  8 } 
  9 
 10 
 11 @Component 
 12 @Order(valor = 2) 
 13 public class WatchStartCommandSqlRunnerImpl2 implements CommandLineRunner { 
 14 @Override 
 15 public void run(String... args) lança Exception { 
 16 System.out.println("第二个Comando执行"); 
 17} 
 18} 
 19

As informações impressas pelo console são as seguintes:

  1 O primeiro comando é executado 
  2 O segundo comando é executado

Com base na verificação acima, podemos implementar a interface CommandLineRunner e reescrever seu método de retorno de chamada de bean run() para implementar o método de criação de tabela apenas uma vez após o início do Springboot. Para implementar a função de criação de tabelas na inicialização do projeto, você também pode precisar determinar se já existe um banco de dados correspondente. Caso contrário, você deve primeiro criar um novo banco de dados. Ao mesmo tempo, você deve considerar a situação em que não há banco de dados correspondente. Portanto, conectamos ao MySQL pela primeira vez através do jdbc. , você deve conectar uma biblioteca existente que vem com ele. Após cada instalação do MySql ser bem-sucedida, haverá uma biblioteca mysql. Ao estabelecer uma conexão jdbc pela primeira vez, você pode conectar-se a ela primeiro.

imagem-20210315080736373

código mostrado abaixo:

Class.forName("com.mysql.jdbc.Driver"); 
String url="jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=UTF-8&ueSSL=false&serverTimezone=GMT%2B8"; 
Conexão conn= DriverManager.getConnection(url,"root","root");

Após estabelecer uma conexão com o software MySql, primeiro crie um objeto Statement, que é um objeto em jdbc que pode ser usado para executar instruções SQL estáticas e retornar os resultados que gera. Ele pode ser usado aqui para realizar as funções de busca de bibliotecas e criação de bibliotecas.

  1 //Cria objeto Statement 
  2 Statement statment=conn.createStatement(); 
  3 /** 
  4 Use o método de consulta do statment executeQuery("show databases like \"fte\"") 
  5 Verifique se o MySql possui o banco de dados fte 
  6 ** / 
  7 ResultSet resultSet=statment.executeQuery("mostra bancos de dados como \"fte\""); 
  8 //Se resultSet.next() for verdadeiro, prova que já existe; 
  9 //Se for falso, prova que a biblioteca ainda não existe, então Execute statment.executeUpdate("create database fte") para criar uma biblioteca 
 10 if(resultSet.next()){ 
 11 log.info("O banco de dados já existe"); 
 12 }else { 
 13 log.info("O banco de dados não existe, primeiro crie o banco de dados fte"); 
 14 if(statment.executeUpdate("crie o banco de dados fte")==1){ 
 15 log.info("Novo banco de dados com sucesso"); 
 16 } 
 17 }

Após a conclusão da criação automática do banco de dados FTE, você poderá criar tabelas na biblioteca FTE.

Encapsulei todos os métodos relacionados de criação de tabelas na classe SqlSessionFactory. Os métodos relacionados de criação de tabelas também precisam usar a conexão do jdbc para se conectar ao banco de dados. Portanto, as variáveis ​​​​de referência de conexão conectadas precisam ser passadas como parâmetros para o inicial construtor de SqlSessionFactory:

  1 public void createTable (conexão conexão, declaração estatística) lança SQLException { 
  2 tentativa { 
  3 
  4 String url="jdbc:mysql://127.0.0.1:3306/fte?useUnicode=true&characterEncoding=UTF-8&ueSSL=false&serverTimezone=GMT%2B8 "; 
  5 conn=DriverManager.getConnection(url,"root","root"); 
  6 SqlSessionFactory sqlSessionFactory=new SqlSessionFactory(conn); 
  7 sqlSessionFactory.schemaOperationsBuild("criar"); 
  8 } catch (SQLException e) { 
  9 e.printStackTrace(); 
 10 }finalmente { 
 11 stat.close(); 
 12 conexão.close(); 
 13} 
 14}

Após inicializar o novo SqlSessionFactory(conn), você pode usar o objeto Connection que foi conectado neste objeto.

  1 classe pública SqlSessionFactory{ 
  2 conexão de conexão privada; 
  3 public SqlSessionFactory(Conexão conexão) { 
  4 this.connection = conexão; 
  5 } 
  6 ...... 
  7 }

Existem duas situações em que os parâmetros podem ser passados ​​​​aqui, a saber, "create" representa a função de criação de uma estrutura de tabela e "drop" representa a função de exclusão da estrutura da tabela:

  1 sqlSessionFactory.schemaOperationsBuild("criar");

Entrando neste método, você primeiro fará um julgamento——

  1 public void schemaOperationsBuild(String type) { 
  2 switch (type){ 
  3 case "drop": 
  4 this.dbSchemaDrop();break; 
  5 caso "criar": 
  6 this.dbSchemaCreate();break; 
  7} 
  8}

Se for this.dbSchemaCreate(), execute a operação de criação da tabela:

  1 /** 
  2 * Adicionar nova tabela de banco de dados 
  3 */ 
  4 public void dbSchemaCreate() { 
  5 
  6 if (!this.isTablePresent()) { 
  7 log.info("Iniciar execução da operação de criação"); 
  8 this.executeResource( "create", "act"); 
  9 log.info("Execução da criação concluída"); 
 10 } 
 11 }

this.executeResource("create", "act") representa a criação de uma tabela de banco de dados chamada act——

  1 public void executeResource (operação String, componente String) { 
  2 this.executeSchemaResource (operação, componente, this.getDbResource (operação, operação, componente), false); 
  3}

Entre eles, this.getDbResource(operação, operação, componente) é o caminho para obter o script sql. Insira o método e você poderá ver——

  1 public String getDbResource (diretório String, operação String, componente String) { 
  2 return "static/db/" + diretório + "/mysql." + operação + "." + componente + ".sql"; 
  3}

Em seguida, leia o script sql no caminho e gere o fluxo de bytes do fluxo de entrada:

  1 public void executeSchemaResource (operação String, componente String, String resourceName, boolean isOptional) { 
  2 InputStream inputStream = null; 
  3 
  4 try { 
  5 //读取sql脚本数据
  6 inputStream = IoUtil.getResourceAsStream(resourceName); 
  7 if (inputStream == null) { 
  8 if (!isOptional) { 
  9 log.error("recurso '" + resourceName + "' não está disponível"); 
 10 retorno; 
 11 } 
 12 } else { 
 13 this.executeSchemaResource(operação, componente, resourceName, inputStream); 
 14 } 
 15 } finalmente { 
 16 IoUtil.closeSilently(inputStream); 
 17} 
 18 
 19}

Finalmente, a implementação principal de todo o script sql é implementada neste método.executeSchemaResource(operação, componente, resourceName, inputStream)——

  1 /** 
  2 * Executar script sql 
  3 * @param operação 
  4 * @param componente 
  5 * @param resourceName 
  6 * @param inputStream 
  7 */ 
  8 private void executeSchemaResource(String operação, String componente, String resourceName, InputStream inputStream) { 
  9 // string de emenda de instrução sql 
 10 String sqlStatement = null; 
 11 Object exceçãoSqlStatement = null; 
 12 
 13 try { 
 14 /** 
 15 * 1.jdbc conecta-se ao banco de dados mysql 
 16 */ 
 17 Connection connection = this.connection; 
 18 
 19 Exception exceção = null; 
 20 /** 
 21 * 2. Leia os dados do script sql em "static/db/create/mysql.create.act.sql" em linhas separadas 
 22 */ 
 23 byte[] bytes = IoUtil.readInputStream(inputStream , resourceName); 
 24 /** 
 25 * 3. Converta as linhas de dados no arquivo sql em strings. Onde a linha quebra, use o caractere de escape "\n" 
 26 */ 
 27 String ddlStatements = new String(bytes); 
 28 /** 
 29 * 4. Ler dados da string na forma de fluxo de caracteres 
 30 */ 
 31 BufferedReader reader = new BufferedReader(new StringReader(ddlStatements)); 
 32 /** 
 33 * 5. De acordo com o caractere de escape na string " \n" Ler em linhas separadas 
 34 */ 
 35 String line = IoUtil.readNextTrimmedLine(reader); 
 36 /** 
 37 * 6. Loop para ler cada linha 
 38 */ 
 39 for(boolean inOraclePlsqlBlock = false; line != null ; line = IoUtil.readNextTrimmedLine(reader)) { 
 40 /** 
 41 * 7. Se ainda houver dados na próxima linha, isso prova que nem todos foram lidos e a leitura 
 42 */ 
 43 ainda pode ser executado if (line.length() > 0) { 
 44 /** 
 45 8. Quando não há emenda suficiente para criar uma instrução de tabela completa,! line.endsWith(";") será verdadeiro, 
 46 ​​ou seja, ele fará um loop contínuo para emenda. Quando encontrar ";", ele saltará da instrução if 
 47 **/ 
 48 if ((!line.endsWith (";") || inOraclePlsqlBlock ) && (!line.startsWith("/") || !inOraclePlsqlBlock)) { 
 49 sqlStatement = this.addSqlStatementPiece(sqlStatement, line); 
 50 } else { 
 51 /** 
 52 9. Se o símbolo " for encontrado durante a emenda do loop;" significa que ele foi emendado para formar uma instrução completa de criação de tabela SQL, como 
 53 criar tabela ACT_GE_PROPERTY ( 
 54 NAME_ varchar(64), 
 55 VALUE_ varchar(300), 
 56 REV_ inteiro, 
 57 chave primária (NAME_) 
 58) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin
 59 Desta forma, você pode primeiro executar a instrução de criação de tabela no banco de dados através do código, como segue: 
 60 **/ 
 61 if (inOraclePlsqlBlock) { 
 62 inOraclePlsqlBlock = false; 
 63 } else { 
 64 sqlStatement = this.addSqlStatementPiece(sqlStatement, line.substring(0, line.length() - 1)); 
 65 } 
 66 /** 
 67 * 10. Compacte a string de instrução de criação da tabela em um objeto Statement 
 68 */ 
 69 Statement jdbcStatement = connection.createStatement(); 
 70 
 71 try { 
 72 /** 
 73 * 11. Finalmente, execute a instrução de criação da tabela no banco de dados 
 74 */ 
 75 log.info("SQL: {}", sqlStatement); 
 76 jdbcStatement.execute(sqlStatement); 
 77 jdbcStatement. close (); 
 78 } catch (Exception var27) { 
 79 log.error("problema durante o esquema {}, instrução {}", new Object[]{operação, sqlStatement, var27}); 
 80 } finalmente { 
 81 /** 
 82 * 12. Nesta etapa, significa que a execução da instrução de criação da tabela SQL anterior foi finalizada. 
 83 * Se nenhum erro ocorrer, foi provado que a primeira estrutura da tabela do banco de dados foi criada. 
 84 * Você pode começar a emendar a próxima instrução de criação de tabela. Instrução, 
 85 */ 
 86 sqlStatement = null; 
 87 } 
 88 } 
 89 } 
 90 } 
 91 
 92 if (exception != null) { 
 93 throw exceção; 
 94 } 
 97 } catch (Exception var29) { 
 98 log .error("não foi possível " + operação + " esquema db: " + exceçãoSqlStatement, var29); 
 99 } 
100 }

Copiar código

A principal função desta parte do código é primeiro ler os dados no script sql na forma de um fluxo de bytes, convertê-los em uma string e substituir as quebras de linha pelo caractere de escape "/n". Em seguida, converta a string em um fluxo de caracteres BufferedReader para ler, divida a leitura de cada linha de acordo com a correspondência "/n", faça um loop para unir cada linha da string lida, quando o loop atingir uma determinada linha e encontrar ";" , que significa que ele foi dividido em uma instrução de criação de tabela completa, semelhante a este formato——

  1 criar tabela ACT_PROCDEF_INFO ( 
  2 ID_ varchar(64) não nulo, 
  3 PROC_DEF_ID_ varchar(64) não nulo, 
  4 REV_ inteiro, 
  5 INFO_JSON_ID_ varchar(64), 
  6 chave primária (ID_) 
  7) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin;

Neste momento, você pode primeiro colocar a string de criação da tabela emendada no banco de dados por meio da instrução jdbcStatement.execute(sqlStatement). Quando a execução é bem-sucedida, a tabela ACT_PROCDEF_INFO significa que ela foi criada com sucesso e, em seguida, a próxima linha continua a ser lida na forma de um fluxo de caracteres BufferedReader para construir a próxima estrutura da tabela de banco de dados.

Todo o processo provavelmente é baseado nesta lógica. Com base nisso, você pode projetar instruções SQL para estruturas de construção de tabelas mais complexas. Quando o projeto for iniciado, você pode executar as instruções SQL correspondentes para construir a tabela.

O código de demonstração foi carregado no git e pode ser baixado e executado diretamente: GitHub - z924931408/Springboot-AutoCreateMySqlTable: imita o mecanismo de criação automática de tabela do mecanismo de fluxo de trabalho Atividade para realizar o Springboot gera automaticamente demonstrações de banco de dados e tabelas na inicialização

Acho que você gosta

Origin blog.csdn.net/weixin_40706420/article/details/135436966
Recomendado
Clasificación