[JAVA] Résumé de ce que j'ai appris dans Spring Batch

J'ai résumé ce que j'ai appris dans Spring Batch, je vais donc écrire un article. En ce qui concerne la construction de l'environnement de Spring Batch, je l'ai fait sur la base de l'article suivant.

Je me fâche si je n'ai pas de table pour Spring Batch

Quand j'ai essayé de l'exécuter dans l'environnement que j'ai construit lorsque j'ai construit l'environnement en référence à divers sites, j'étais en colère qu'il n'y avait pas de table telle que "BATCH_JOB_EXECUTION". Il semble qu'il soit nécessaire de préparer une table dédiée pour exécuter Spring Batch. Voir ci-dessous pour les tableaux requis.

Cependant, il semble difficile de les insérer vous-même. Par conséquent, Spring prépare SQL pour diverses plates-formes. Si vous recherchez "spring batch schema-〇〇 (nom de la plateforme) .sql", vous trouverez un hit. J'ai utilisé postgresql, donc j'ai utilisé "shema-postgresql.sql". J'ai évoqué ce qui suit.

・ [Schema-postgresql.sql](https://github.com/spring-projects/spring-batch/blob/master/spring-batch-core/src/main/resources/org/springframework/batch/core/schema -postgresql.sql)

Initialiser la table lors de l'exécution de l'application

Je pense que vous devrez démarrer l'application plusieurs fois pour vérifier le fonctionnement de l'application, mais je pense qu'il est difficile de créer des données à chaque fois. Par conséquent, nous répondrons en initialisant la table à chaque fois. Spring Boot fournit un mécanisme pour initialiser la table lorsque l'application est exécutée. Je me suis référé au site suivant.

Il semble que si vous placez "schema-〇〇.sql" sous "src / main / resources" créé lors de la création du projet Spring Boot, il démarrera automatiquement. J'ai utilisé postgresql, donc j'ai fait ce qui suit:

Décrivez ce qui suit dans application.properties

application.properties


spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
#spring.datasource.username=postgres
#spring.datasource.password=postgres
spring.datasource.initialization-mode=always

Il semble que SpringBoot définit automatiquement un bean pour DataSource et que l'ingénieur n'a besoin que de décrire le paramètre DB dans application.properties.

Préparer SQL

En plus de ce que j'ai confirmé dans "[Je me fâche s'il n'y a pas de table pour Spring Batch](https://qiita.com/kyabetsuda/items/f011533621cff7f53c63# je me fâche s'il n'y a pas de table pour Spring Batch)", c'est comme suit. SQL est préparé.

schema-all.sql


DROP TABLE IF EXISTS people;

CREATE TABLE people  (
    person_id SERIAL NOT NULL PRIMARY KEY,
    first_name VARCHAR(20),
    last_name VARCHAR(20)
);

-- Autogenerated: do not edit this file
DROP TABLE  IF EXISTS BATCH_STEP_EXECUTION_CONTEXT;
DROP TABLE  IF EXISTS BATCH_JOB_EXECUTION_CONTEXT;
DROP TABLE  IF EXISTS BATCH_STEP_EXECUTION;
DROP TABLE  IF EXISTS BATCH_JOB_EXECUTION_PARAMS;
DROP TABLE  IF EXISTS BATCH_JOB_EXECUTION;
DROP TABLE  IF EXISTS BATCH_JOB_INSTANCE;

DROP SEQUENCE  IF EXISTS BATCH_STEP_EXECUTION_SEQ ;
DROP SEQUENCE  IF EXISTS BATCH_JOB_EXECUTION_SEQ ;
DROP SEQUENCE  IF EXISTS BATCH_JOB_SEQ ;

CREATE TABLE BATCH_JOB_INSTANCE  (
	JOB_INSTANCE_ID BIGINT  NOT NULL PRIMARY KEY ,
	VERSION BIGINT ,
	JOB_NAME VARCHAR(100) NOT NULL,
	JOB_KEY VARCHAR(32) NOT NULL,
	constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY)
) ;

CREATE TABLE BATCH_JOB_EXECUTION  (
	JOB_EXECUTION_ID BIGINT  NOT NULL PRIMARY KEY ,
	VERSION BIGINT  ,
	JOB_INSTANCE_ID BIGINT NOT NULL,
	CREATE_TIME TIMESTAMP NOT NULL,
	START_TIME TIMESTAMP DEFAULT NULL ,
	END_TIME TIMESTAMP DEFAULT NULL ,
	STATUS VARCHAR(10) ,
	EXIT_CODE VARCHAR(2500) ,
	EXIT_MESSAGE VARCHAR(2500) ,
	LAST_UPDATED TIMESTAMP,
	JOB_CONFIGURATION_LOCATION VARCHAR(2500) NULL,
	constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID)
	references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID)
) ;

CREATE TABLE BATCH_JOB_EXECUTION_PARAMS  (
	JOB_EXECUTION_ID BIGINT NOT NULL ,
	TYPE_CD VARCHAR(6) NOT NULL ,
	KEY_NAME VARCHAR(100) NOT NULL ,
	STRING_VAL VARCHAR(250) ,
	DATE_VAL TIMESTAMP DEFAULT NULL ,
	LONG_VAL BIGINT ,
	DOUBLE_VAL DOUBLE PRECISION ,
	IDENTIFYING CHAR(1) NOT NULL ,
	constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;

CREATE TABLE BATCH_STEP_EXECUTION  (
	STEP_EXECUTION_ID BIGINT  NOT NULL PRIMARY KEY ,
	VERSION BIGINT NOT NULL,
	STEP_NAME VARCHAR(100) NOT NULL,
	JOB_EXECUTION_ID BIGINT NOT NULL,
	START_TIME TIMESTAMP NOT NULL ,
	END_TIME TIMESTAMP DEFAULT NULL ,
	STATUS VARCHAR(10) ,
	COMMIT_COUNT BIGINT ,
	READ_COUNT BIGINT ,
	FILTER_COUNT BIGINT ,
	WRITE_COUNT BIGINT ,
	READ_SKIP_COUNT BIGINT ,
	WRITE_SKIP_COUNT BIGINT ,
	PROCESS_SKIP_COUNT BIGINT ,
	ROLLBACK_COUNT BIGINT ,
	EXIT_CODE VARCHAR(2500) ,
	EXIT_MESSAGE VARCHAR(2500) ,
	LAST_UPDATED TIMESTAMP,
	constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;

CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT  (
	STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
	SHORT_CONTEXT VARCHAR(2500) NOT NULL,
	SERIALIZED_CONTEXT TEXT ,
	constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
	references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID)
) ;

CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT  (
	JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
	SHORT_CONTEXT VARCHAR(2500) NOT NULL,
	SERIALIZED_CONTEXT TEXT ,
	constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;

CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ MAXVALUE 9223372036854775807 NO CYCLE;
CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ MAXVALUE 9223372036854775807 NO CYCLE;
CREATE SEQUENCE BATCH_JOB_SEQ MAXVALUE 9223372036854775807 NO CYCLE;

Désormais, les tables nécessaires à l'exécution sont initialisées après chaque exécution.

Le travail se boucle indéfiniment

Spring Batch utilise essentiellement les classes Reader, Processor et Writer pour le traitement, mais lorsque je les ai définis moi-même, une boucle infinie s'est produite. J'ai eu beaucoup de problèmes, mais le site suivant a été utile

Spring Batch semble boucler jusqu'à ce que ItemReader renvoie null. Par conséquent, il semble qu'ItemReader doit être conçu et implémenté pour que null soit retourné lorsque le traitement est terminé. Il a été mis en œuvre de cette façon sur les sites suivants.

Ce qui suit est implémenté en référence au site ci-dessus.

PersonItemReader.java


import java.util.List;

import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.beans.factory.annotation.Autowired;

public class PersonItemReader implements ItemReader<Person>{

	private List<Person> people = null;
	private int nextIndex;
	private final PersonService service;

	public PersonItemReader(PersonService service) {
		this.service = service;
	}

	@Override
	public Person read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
		if (people == null) {
			people = service.selectAll();
			nextIndex = 0;
		}
		Person person = null;
		if (nextIndex < people.size()) {
            person = people.get(nextIndex);
            nextIndex++;
        }
        return person;
	}

}

Le ItemReader défini est une définition de Bean.

BatchConfiguration.java


@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

...(réduction)...

    @Autowired
    PersonService personService;

    @Bean
    public PersonItemReader reader() {
    	return new PersonItemReader(personService);
    }

...(réduction)...

}

Méthode générale de mise en œuvre du lecteur / processeur / écrivain

Dans Spring Batch, il est courant d'utiliser Reader, Processor et Writer, mais il existe des méthodes d'implémentation générales pour chacun. Je pense que c'est comme un modèle de conception. Vous trouverez ci-dessous une description de la méthode de mise en œuvre que j'ai effectuée. Le premier est la classe Reader.

PersonItemReader.java


import java.util.ArrayList;
import java.util.List;

import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;

public class PersonItemReader implements ItemReader<List<Person>>{

	private final PersonService service;
	private final PersonCheckService checkService;

	public PersonItemReaderForTest(PersonService service, PersonCheckService checkService) {
		this.service = service;
		this.checkService = checkService;
	}

	@Override
	public List<Person> read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
		List<Person> people = service.selectAll();
		List<Person> ret = null;
		for(Person person : people) {
			if(checkService.check(person)) {
				if(ret == null)
					ret = new ArrayList<Person>();
				ret.add(person);
			}
		}
		return ret;
	}

}

Celui obtenu de la base de données est passé au processeur sous forme de liste. Vient ensuite la classe Processor.

PersonItemProcessor.java


import java.util.ArrayList;
import java.util.List;

import org.springframework.batch.item.ItemProcessor;

public class PersonItemProcessor implements ItemProcessor<List<Person>, List<Person>> {

	@Override
    public List<Person> process(final List<Person> people) throws Exception {
    	List<Person> transformedPeople = new ArrayList<Person>();
    	for(Person person : people) {
          final String firstName = person.getFirstName().toUpperCase();
          final String lastName = person.getLastName().toUpperCase();
          final Person transformedPerson = new Person(firstName, lastName);
          transformedPeople.add(transformedPerson);
    	}
    	return transformedPeople;
    }

}

La liste transmise par Reader est traitée et une nouvelle liste est renvoyée. Vient ensuite la classe Writer.

PersonItemWriter.java


import java.util.List;

import org.springframework.batch.item.ItemWriter;

public class PersonItemWriterForTest implements ItemWriter<Object>{

	PersonService service;

	public PersonItemWriterForTest(PersonService service) {
		this.service = service;
	}

	@Override
	public void write(List<? extends Object> items) throws Exception {
		List<Person> people = (List<Person>) items.get(0);
		for(Person person : people) {
			service.updatePerson(person);
		}

	}

}

Dans la classe Writer, la liste transmise par le processeur est enregistrée dans la base de données. Mais il convient de noter le code suivant dans la classe Writer

List<Person> people = (List<Person>) items.get(0);

Pour obtenir List , récupérez (0) dans le paramètre List. En d'autres termes, List \ <List \ <Person > > est passé comme paramètre. Je me sentais un peu mal à l'aise avec ça. Au début, je pensais que List \ <Person > viendrait en paramètre, et quand je l'ai implémenté, j'ai remarqué que le comportement devenait étrange. Je me suis demandé pourquoi, et quand je l'ai cherché, il y avait une personne qui avait la même question.

Ce qui suit est écrit sur le site ci-dessus.

Typically, the design pattern is:

Reader -> reads something, returns ReadItem Processor -> ingests ReadItem, returns ProcessedItem Writer -> ingests List<ProcessedItem>

If your processor is returning List<Object>, then you need your Writer to expect List<List<Object>>.

En règle générale, le modèle de conception est Reader, le processeur renvoie un seul élément et Writer traite une liste d'éléments. Étant donné que le traitement par lots est effectué, j'ai pensé qu'il serait normal de transmettre plusieurs données acquises de la base de données au processeur sous forme de liste. Cependant, dans Writer, il semble que le processeur puisse recevoir l'objet retourné tel qu'il est stocké dans la liste. Par conséquent, en renvoyant un seul objet avec Processor, il n'est pas nécessaire d'effectuer le traitement ci-dessus de items.get (0). Reader a également une méthode d'implémentation qui renvoie un seul objet comme présenté dans "[Le travail se boucle infiniment](https://qiita.com/kyabetsuda/items/f011533621cff7f53c63#Job boucle infiniment)". Cela semble commun.

Tester les tâches et les étapes

Spring Batch fournit un mécanisme pour tester les tâches et les étapes. Utilisez JobLauncherTestUtils pour tester. Je me suis référé au site suivant.

Test unitaire

Tout d'abord, définissez un bean pour utiliser JobLauncherTestUtils.

BatchConfigurationForTest.java


import javax.sql.DataSource;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

@Configuration
@EnableBatchProcessing
public class BatchConfigurationForTest {

    @Autowired
    public JobBuilderFactory jobBuilderFactory;

    @Autowired
    public StepBuilderFactory stepBuilderFactory;

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    JobRepository jobRepository;

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.postgresql.Driver");
        dataSource.setUrl("jdbc:postgresql://localhost:5432/ec");
//        dataSource.setUsername(username);
//        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    PersonService personService() {
    	return new PersonService();
    };

    @Bean
    public PersonItemReader reader() {
    	return new PersonItemReader(personService());
    }

    @Bean
    public PersonItemProcessor processor() {
        return new PersonItemProcessor();
    }

    @Bean
    public JdbcBatchItemWriter<Person> writer(DataSource dataSource) {
        return new JdbcBatchItemWriterBuilder<Person>()
            .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
            .sql("INSERT INTO people (first_name, last_name) VALUES (:firstName, :lastName)")
            .dataSource(dataSource)
            .build();
    }

    @Bean
    public NoWorkFoundStepExecutionListener noWorkFoundStepExecutionListener() {
    	return new NoWorkFoundStepExecutionListener();
    }

    @Bean
    public Job importUserJob(Step step1) {
        return jobBuilderFactory.get("importUserJob")
            .incrementer(new RunIdIncrementer())
            .flow(step1)
            .end()
            .build();
    }

    @Bean
    public Step step1(NoWorkFoundStepExecutionListener listener, JdbcBatchItemWriter<Person> writer) {
        return stepBuilderFactory.get("step1")
            .<Person, Person> chunk(1)
            .reader(reader())
            .processor(processor())
            .writer(writer)
            .listener(listener)
            .build();
    }

    @Bean
    public JobLauncherTestUtils jobLauncherTestUtils() {
    	JobLauncherTestUtils utils = new JobLauncherTestUtils();
    	utils.setJob(importUserJob(step1(noWorkFoundStepExecutionListener(), writer(dataSource()))));
    	utils.setJobLauncher(jobLauncher);
    	utils.setJobRepository(jobRepository);
    	return utils;
    }
}

JobLauncherTestUtils est défini comme un bean en bas. Ce qui précède est une nouvelle définition de la classe Configuration pour les tests. Le contenu lui-même ne change pas beaucoup de la classe Configuration. Dans SpringBatch, vous pouvez définir un "écouteur" après l'étape et faire quelque chose, mais vous pouvez également tester l'écouteur, nous définissons donc un bean ensemble (la classe lister est décrite dans le site de référence ci-dessus) Ça a été). Vient ensuite la classe de test. La première est la classe qui teste le travail.

JobTest.java


import static org.hamcrest.CoreMatchers.*;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=BatchConfigurationForTest.class)
public class JobTest {

	@Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

	@Test
	public void testJob() throws Exception {
		JobExecution jobExecution = jobLauncherTestUtils.launchJob();
        Assert.assertThat("COMPLETED", is(jobExecution.getExitStatus().getExitCode()));
	}

}

Dans la classe de test, le JobLauncherTestUtils défini par Bean est Autowired. Le travail est exécuté par launchJob. La tâche à exécuter est spécifiée lorsque le bean est défini.

Vient ensuite la classe qui teste les étapes.

StepTest.java


import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.MetaDataInstanceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=BatchConfigurationForTest.class)
public class StepTest {

	@Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

	@Autowired
	NoWorkFoundStepExecutionListener tested;

	@Test
	public void testStep() {
		JobExecution jobExecution = jobLauncherTestUtils.launchStep("step1");
		Assert.assertThat("COMPLETED", is(jobExecution.getExitStatus().getExitCode()));
	}

	@Test
	public void testAfterStep() {
	    StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution();

	    stepExecution.setExitStatus(ExitStatus.COMPLETED);
	    stepExecution.setReadCount(0);

	    ExitStatus exitStatus = tested.afterStep(stepExecution);
	    assertThat(ExitStatus.FAILED.getExitCode(), is(exitStatus.getExitCode()));
	}
}

Lors du test d'une étape, passez le nom de l'étape à exécuter dans l'argument de launchStep (). testAfterStep () teste l'écouteur défini par le bean. setReadCount () représente le nombre d'éléments lus par la classe Reader. Le NoWorkFoundStepExecutionListener décrit sur le site de référence est implémenté pour renvoyer ExisStatus.FAILED lorsque getReadCount () == 0.

C'est la fin du résumé de Spring Batch.

Recommended Posts

Résumé de ce que j'ai appris dans Spring Batch
Résumé de ce que j'ai appris sur Spring Boot
Ce que j'ai appris
J'étais accro au @Transactional de Spring
Ce que j'ai appris ② ~ Mock ~
J'ai essayé Spring Batch
Ce que j'ai appris ① ~ DJUnit ~
Ce que j'ai appris en Java (partie 2) Que sont les variables?
Étape de boucle avec Spring Batch
Ce que j'ai appris sur Kotlin
Ce que j'ai appris en Java (partie 4) Branchement conditionnel et répétition
Ce que j'ai appris en étudiant les rails
Spring Framework 5.0 Résumé des principaux changements
J'ai participé au JJUG CCC 2019 Spring
Qu'est-ce que @Autowired dans Spring Boot?
Ce que j'ai appris avec Java Gold
Ce que j'ai appris avec Java Silver
Résumé de «Modèles de conception appris en langage Java (édition multithread)» (partie 7)
Résumé de «Modèles de conception appris en langage Java (édition multithread)» (partie 3)
Résumé de «Modèles de conception appris en langage Java (édition multithread)» (partie 9)
Ce que j'ai appris en Java (partie 1) Flux de développement Java et présentation
Résumé de «Modèles de conception appris en langage Java (édition multithread)» (partie 4)
Résumé de «Modèles de conception appris en langage Java (édition multithread)» (Partie 5)
Résumé des problèmes auxquels je n'ai pas pu me connecter à Firebase
[Note] Ce que j'ai appris en six mois grâce à des inexpérimentés (Java)
[Note] Ce que j'ai appris en six mois grâce à des inexpérimentés (Java) (1)
Résumé de «Modèles de conception appris en langage Java (édition multithread)» (partie 2)
Résumé de «Modèles de conception appris en langage Java (édition multi-thread)» (Partie 1)
Ce que j'ai fait lors de la migration de la série Spring Boot 1.4 vers la série 2.0
[Note] Ce que j'ai appris en six mois grâce à des inexpérimentés (Java) (3)
Ce que j'ai fait lors de la migration de la série Spring Boot 1.5 vers la série 2.0
Résumé de «Modèles de conception appris en langage Java (édition multithread)» (partie 11)
Résumé de «Modèles de conception appris en langage Java (édition multithread)» (partie 12)
Résumé de «Modèles de conception appris en langage Java (édition multithread)» (partie 8)
Ce que j'ai étudié dans le développement de Wagby Note 1
Résumé des classes racine dans différentes langues
Résumé des hachages et symboles dans Ruby
Résumé de la participation au JJUG CCC 2019 Spring
Ce que j'ai appris du calcul monétaire Java
[* Java *] J'ai participé au JJUG CCC 2019 Spring
Résumé de la sélection des éléments dans Selenium
Résumé des nouvelles fonctionnalités ajoutées dans Deeplearning4J 1.0.0-beta4
Un examen rapide de Java appris en classe
J'ai besoin de la validation de Spring Data pour Pageable ~
J'ai essayé Spring.
Premier AWS Lambda (j'ai essayé de voir dans quel type d'environnement il fonctionne)
Résumé des commandes fréquemment utilisées dans Rails et Docker
Un examen rapide de Java appris en classe part4
[Pour les débutants] DI ~ Les bases de DI et DI au printemps ~
05. J'ai essayé de supprimer la source de Spring Boot
J'ai essayé de réduire la capacité de Spring Boot
J'écrirai quand même ce que j'ai appris sur docker (deuxième)
Autoboxing que j'ai appris en tant que NullPointerException dans la comparaison courte
J'ai créé un serveur et un client Restful au printemps.
Résumé personnel des types souvent utilisés dans JUnit 4