27 Temmuz 2022 Çarşamba

Swagger @Schema Anotasyonu

Giriş
Şu satırı dahil ederiz
import io.swagger.v3.oas.annotations.media.Schema;
Örnek
Şöyle yaparız
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; @Data @Schema(name="Book", description="Book characteristic") public class Book { @Schema(description="Book Tittle", maxLength=50) private String title; private String[] authors; }

SpringDoc OpenAPI Kullanımı

springdoc-openapi Nedir?
Açıklaması şöyle. Yani aslıda OpenAPI gerçekleştirimi diyebiliriz. OpenAPI REST noktaları için 
1. JSON veya
2. YAML
dokümantasyonu üretir
springdoc-openapi is a library that automatically generates documentation for OpenAPI compliant REST APIs built with Spring. It inspects an application’s code at runtime, looking at HTTP endpoints, their annotations, and returned objects and uses this information to generate documentation.

The library provides support for standard (don’t know what to really call this) Spring web code (e.g. endpoints defined in @RestControllers/@Controllers) and Spring Webflux.

To top everything off, springdoc-openapi can start a Swagger UI based on the documentation it generates, making it even easier for users to understand and interact with your APIs.
Ancak bu kütüphaneye ilave olarak bir şey daha kullanılıyor. O da swagger-annotations kütüphanesi. Bu kütüphane için ek bir maven dependency gerekmiyor. Açıklaması şöyle. Swagger ise REST noktaları için kullanması kolay ekranlar üretir.
As mentioned above, you will miss out on a lot of documentation if you rely on springdoc-openapi to do everything without you giving it a helping hand.

Luckily, another library provides ways to add this extra documentation that is compatible with springdoc-openapi. The swagger-annotations library gives you access to numerous additional annotations (as the name suggests) to document your APIs as the OpenAPI specification wants you to do.

You will have access to swagger-annotations without further changes as it is an API dependency of springdoc-openapi.
Maven
springdoc-openapi-v1 için şu satırı dahil ederiz
<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-ui</artifactId>
   <version>1.7.0</version>
 </dependency>
springdoc-openapi-v2 ve Spring 3 ile şu satırı dahil ederiz
<dependency>
<groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.0.2</version> </dependency>
Swagger UI
Açıklaması şöyle
The generated documentation can then be accessed by a Swagger UI which is started by default when using springdoc-openapi and is accessible by going to <server:port>/swagger-ui.html, which will redirect you to a slightly different URL straight after (<server:port>/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config). Going to /swagger-ui/index.html will instead display Swagger’s pet store example, which probably isn’t what you wanted to see.
Swagger çıktıyı görmek için şu adrese gideriz 
http://localhost:8080/swagger-ui/index.html
Şeklen şöyle. Ekran tasarımı şöyle
1. Başlık
2. Controller listesi
3. Controller tarafından kullanılan nesnelerin listesi yani Schemas.
Benim yaptığım bir örnek şöyle


application.properties
Açıklaması şöyle
Springdoc-openapi also supports swagger-ui properties. These can be used as Spring Boot properties, with the prefix springdoc.swagger-ui.
Örnek
Open API URL'sini değiştirmek için şöyle yaparız
springdoc.api-docs.path=/docs/v3/api-docs
Örnek
Swagger URL'sini değiştirmek için şöyle yaparız
springdoc.swagger-ui.path=/swagger-ui-custom.html
Örnek
Şöyle yaparız
springdoc.swagger-ui.operationsSorter=method
Maven Plugin - openapi.json
Açıklaması şöyle
Bu plugin ile yaml veya json dosyaları üretilir. Böylece bu dosya Postman, Insomnia veya benzeri araçlara ithal edilebilir. Dosya şöyle bir şey
{
  "paths": {
    "/jobs/{groupId}/{jobId}": {
      "delete": {
        "tags": [
          "api-controller"
        ],
        "operationId": "deleteJob",
        "parameters": [
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          }
        }
      }
    }
  }
}
Örnek
Şöyle yaparız
<plugin>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-maven-plugin</artifactId>
  <version>1.1</version>
  <executions>
    <execution>
      <id>integration-test</id>
      <goals>
        <goal>generate</goal>
      </goals>
    </execution>
  </executions>
</plugin>
Çalıştırmak için şöyle yaparız. Böylece openapi.json dosyası üretilir
mvn verify
Gradle Plugin - openapi.json
Örnek
Şöyle yaparız
plugins {  
  id("org.springdoc.openapi-gradle-plugin") version "1.4.0"  
  id("org.openapi.generator") version "6.1.0" 
}
Şöyle yaparız
// Define a custom openApi task to start Spring and write the OpenAPI spec 
//to the build directory.
openApi { 
  customBootRun { 
    args.set(listOf("--spring.profiles.active=openapi")) 
  } 
}

When we run ./gradlew generateOpenApiDocs an openapi.json file 
will be written to the build directory.
Daha sonra şöyle yaparız
// Now we can generate an OpenAPI client using a gradle task:
openApiGenerate { 
  generatorName.set("java") 
  version.set(System.getenv("CLIENT_VERSION")) 
  id.set("yourid") 
  groupId.set("yourgroup") 
  inputSpec.set("$buildDir/openapi.json") 
  outputDir.set("$buildDir/client") 
  configOptions.set(mapOf( "dateLibrary" to "java8" )) 
}

//Run the command like so ./gradlew openApiGenerate to write 
//a client into the build/client directory. 
@ApiResponses Anotasyonu
@ApiResponses Anotasyonu yazısına taşıdım

@Operation Anotasyonu
@Operation Anotasyonu yazısına taşıdım

@OpenAPIDefinition Anotasyonu
@OpenAPIDefinition Anotasyonu yazısına taşıdım

OpenAPI Sınıfı
OpenAPI Sınıfı yazısına taşıdım

30 Haziran 2022 Perşembe

SpringMVC AbstractController Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.web.servlet.mvc.AbstractController;
Örnek
Şöyle yaparız
public class HelloWorldController extends AbstractController {
  
  @Override
  protected ModelAndView handleRequestInternal(HttpServletRequest request, 
    HttpServletResponse response)throws Exception {

    String name = request.getParameter("name");

    //ModelAndView Object contains the logical view to return,And data model
    ModelAndView mv = new ModelAndView();
    //Set view name,Can be a string or a view object
    mv.setViewName("hello");
    //Set up data model
    mv.addObject("name", name);

    return mv;
  }
}
<bean name="/hello" class="com.briup.web.controller.HelloWorldController">
  <property name="synchronizeOnSession" value="true"></property>
</bean>

30 Mayıs 2022 Pazartesi

SpringMVC HandlerExceptionResolver Arayüzü - Yeni Sayfaya Yönlendirir

Giriş
Şu satırı dahil ederiz
import org.springframework.web.servlet.HandlerExceptionResolver;
Exception döndürmek için 3 yöntem var
- @ControllerAdvice + @ExceptionHandler : Global Exception handling yapar
- HandlerExceptionResolver
- ResponseStatusException
Bu sınıf direkt ResponseStatusException fırlatmaktan daha iyi, çünkü merkezi bir noktadan Exception'lar kontrol edilebilir. Bu sınıf yerine @ControllerAdvice veya @ExceptionHandler kullanılabilir.

Filtrelerden Kullanmak
Şöyle yaparız
public class AuthorizationFilter extends BasicAuthenticationFilter {
  private final HandlerExceptionResolver exceptionResolver;

  public AuthorizationFilter(AuthenticationManager authenticationManager, 
                             HandlerExceptionResolver exceptionResolver) {
    super(authenticationManager);
    this.exceptionResolver = exceptionResolver;
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain chain)
            throws IOException, ServletException {
    try {
    ...
     } catch (ExpiredJwtException e) {
       exceptionResolver.resolveException(request, response, null, e);
    }
  }
}
Örnek
Şöyle yaparız
@Component
class RestResponseStatusExceptionResolver implements HandlerExceptionResolver {

  @Override
  public ModelAndView resolveException(HttpServletRequest request, 
                                       HttpServletResponse response, 
                                       Object object, 
                                       Exception exception) {
    ModelAndView model = new ModelAndView();
    model.setView(new MappingJackson2JsonView());
    model.addObject("exception", exception.getMessage());
    return model;
  }
}

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  @Override
  public void configureHandlerExceptionResolvers(
    List<HandlerExceptionResolver> resolvers) {
    
    resolvers.add(0, new RestResponseStatusExceptionResolver());
  }
}
HTTP cevabı şöyledir
{
  "serviceDownTimeException": {
    "cause": null,
    "stackTrace": [],
    "localizedMessage": "1-2 AM is service downtime!",
    "message": "1-2 AM is service downtime!",
    "suppressed": []
  }
}


SpringMVC ResponseStatusException Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.web.server.ResponseStatusException;
Bu sınıfın iki kullanım yöntemi var
1. Servis kodundan fırlatılır ve istemciye kadar gitmesine izin verilir
2. Servis kodundan fırlatılır ve @ExceptionHandler(ResponseStatusException.class) ile yakalanarak bir exception sonucu nesnesine çevrilir.


Ancak bu iki yöntem yerine şu yöntem tercih edilebilir. Açıklaması şöyle
This exception can even be used at the service level and that approach can help you in terms of the number of exceptions you are creating. But as you can probably guess by now it is not a good solution for unified exception handling compared to ControllerAdvice or HandlerExceptionResolver.
Örnek
Şöyle yaparız
@RestController
@RequestMapping("/api/v1/")
public class StudentRestController {

  @Autowired
  StudentService studentService;

  @GetMapping("student/{id}")
  public ResponseEntity<Student> getUser(
    @PathVariable(name = "id", required = false) Long id  ) {
    Student student;
    try {
      student = studentService.findStudentById(null);
    } catch (InvalidIdException e) {
      throw new ResponseStatusException(HttpStatus.BAD_REQUEST, 
        e.getMessage(), e);
    } catch (ServiceDownTimeException e) {
      throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, 
        e.getMessage(), e);
    }
    return ResponseEntity
      .status(HttpStatus.OK)
      .body(student);
  }
}
HTTP sonucu şöyle
{
  "timestamp": "2022-05-20T19:17:54.442+00:00",
  "status": 503,
  "error": "Service Unavailable",
  "message": "1-2 AM is service downtime!",
  "path": "/api/v1/student/1"
}
Örnek - Kalıtım
Şöyle yaparız
public class NoSuchElementFoundException extends ResponseStatusException {

  public NoSuchElementFoundException(String message){
    super(HttpStatus.NOT_FOUND, message);
  }

  @Override
  public HttpHeaders getResponseHeaders() {
      // return response headers
  }
}
getStatusCode metodu
Spring 3 ile ResponseStatusException -> getStatus() metodu yerine getStatusCode() metodu geldi

Örnek
Şöyle yaparız
@ExceptionHandler(ResponseStatusException.class) public ResponseEntity<ErrorResponse> handleNotFound(ResponseStatusException exception) { final ErrorResponse errorResponse = new ErrorResponse(); // errorResponse.setHttpStatus(exception.getStatus().value()); errorResponse.setHttpStatus(exception.getStatusCode().value()); errorResponse.setException(exception.getClass().getSimpleName()); errorResponse.setMessage(exception.getMessage()); // return new ResponseEntity<>(errorResponse, exception.getStatus()); return new ResponseEntity<>(errorResponse, exception.getStatusCode()); }



29 Mayıs 2022 Pazar

SpringData JpaRepository İle JpaSpecification - Karmaşık Select Sorguları İçindir

Giriş
Şu satırı dahil ederiz
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.domain.Specification;
Açıklaması şöyle. Dynamic DB Query içindir
JPA 2 introduces a criteria API that you can use to build queries programmatically. By writing a criteria, you define the where clause of a query for a domain class. Taking another step back, these criteria can be regarded as a predicate over the entity that is described by the JPA criteria API constraints.

Spring Data JPA takes the concept of a specification from Eric Evans' book, “Domain Driven Design”, following the same semantics and providing an API to define such specifications with the JPA criteria API. To support specifications, you can extend your repository interface with the JpaSpecificationExecutor interface
Specification Neden Lazım
Bir sürü isteğe bağlı parametre alan bir sorgumuz 

SQL ile şöyle yaparız. Her parametre isteğe bağlı olduğu için coalesce ile kullanmak gerekiyor.
@Query( "select * from user u left outer join education_detail e on u.id = e.user_id where (coalesce(:userStatuses) is null or (u.userStatus in (:userStatuses))) and (coalesce(:ageGreaterThanOrEqualTo) is null or (u.age >= (:ageGreaterThanOrEqualTo))) and (coalesce(:gradeGreaterThanOrEqualTo) is null or (e.grade in (:gradeGreaterThanOrEqualTo)))") List<User> findAllUsers( List<String> userStatuses, Integer ageGreaterThanOrEqualTo, Double gradeGreaterThanOrEqualTo );
Derived query ile şöyle yaparız
List<User> findByUserStatusIn(List<String> userStatuses); List<User> findByUserStatusInAndAgeGreaterThanOrEqualTo(List<String> userStatuses, Integer ageGreaterThanOrEqualTo); List<User>findByUserStatusInAndAgeGreaterThanOrEqualToAndGradeGreaterThanOrEqualTo( List<String> userStatuses, Integer ageGreaterThanOrEqualTo, Double gradeGreaterThanOrEqualTo);
Her ikisi de karışık çözümler

Bunun yerine org.springframework.data.jpa.domain.Specification arayüzünden kalıtan bir sınıf yazıyoruz. Sınıf şöyle
static Specification<User> userStatusesIn(List<String> userStatuses) { return (root, query, builder) -> { if(CollectionUtils.isEmpty(userStatuses)){ return builder.conjunction(); } return root.get(User_.status).in(userStatuses); }; }
Böylece şu SQL otomatik üretiliyor.
where (coalesce(:userStatuses) is null or (u.userStatus in (:userStatuses)))

Repository Tanımlama
Repository kodu artık şöyle oluyor
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> { … }
Bu arayüz ile gelen metodlar  şöyle
public interface JpaSpecificationExecutor<T> { Optional<T> findOne(@Nullable Specification<T> spec); List<T> findAll(@Nullable Specification<T> spec); Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable); List<T> findAll(@Nullable Specification<T> spec, Sort sort); long count(@Nullable Specification<T> spec); }
Specification Tanımlama
and(), not(), or(), toPredicate(), where() metodları var. Amacımız bir Specification nesnesi yaratıp bunu Repository nesnesinin findX() metodlarından birisine geçmek

toPredicate metodu
İmzası şöyle. Diğer metodlar ya default ya da static ancak eğer Specification arayüzünden kalıtırsak bu metodu kodlamak gerekiypr
public interface Specification<T> { Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder); }
Örnek - Builder Kullanan Specification
Servis sınıfımız şöyle olsun
@Autowired private EmployeeRepository employeeRepo; @Transactional(readOnly = true) public List<EmployeeVO> listEmployeesUsingSpecification(int page, int pageSize, String sortField, Sort.Direction sortDirection, LocalDate birthDate, LocalDate hireDate, String title, Integer salary, String searchTerm) { Pageable pageable = pageable(page, pageSize, sortField, sortDirection); Specification<Employee> spec = EmployeeSpecification.builder() .birthDate(birthDate) .hireDate(hireDate) .salary(salary) .title(title) .searchTerm(searchTerm) .build(); Page<Employee> employees = employeeRepo.findAll(spec, pageable); return employees.stream() .map(EmployeeMapper::map) .collect(Collectors.toList()); } private Pageable pageable(int page, int pageSize, String sortField, Direction sortDirection) { return PageRequest.of(page, pageSize, sortDirection, sortField); }
Builder kullanan Specification için şöyle yaparız
@Data @Builder public class EmployeeSpecification implements Specification<Employee> { private static final LocalDate CURRENTLY_ACTIVE_ENTRY = LocalDate.of(9999, 1, 1); private LocalDate birthDate; private LocalDate hireDate; private Integer salary; private String title; private String searchTerm; @Override public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) { Predicate birthDatePred = ofNullable(birthDate) .map(b -> equals(cb, root.get("birthDate"), birthDate)) .orElse(null); Predicate hireDatePred = ofNullable(hireDate) .map(h -> equals(cb, root.get("hireDate"), hireDate)) .orElse(null); Predicate salaryPred = salaryPredicate(root, cb); Predicate titlePred = titlePredicate(root, cb); if (nonNull(salaryPred) || nonNull(titlePred)) { query.distinct(true); } Predicate searchPred = null; if (StringUtils.isNoneBlank(searchTerm)) { Predicate firstNamePred = like(cb, root.get("firstName"), searchTerm); Predicate lastNamePred = like(cb, root.get("lastName"), searchTerm); searchPred = cb.or(firstNamePred, lastNamePred); } List<Predicate> predicates = new ArrayList<>(); ofNullable(birthDatePred).ifPresent(predicates::add); ofNullable(hireDatePred).ifPresent(predicates::add); ofNullable(salaryPred).ifPresent(predicates::add); ofNullable(titlePred).ifPresent(predicates::add); ofNullable(searchPred).ifPresent(predicates::add); return cb.and(predicates.toArray(new Predicate[predicates.size()])); } private Predicate salaryPredicate(Root<Employee> root, CriteriaBuilder cb) { if (isNull(salary)) { return null; } Join<Employee, Title> salaryJoin = root.join("salaries", JoinType.INNER); int salaryLow = Double.valueOf(salary * 0.9).intValue(); int salaryHigh = Double.valueOf(salary * 1.1).intValue(); return cb.and( between(cb, salaryJoin.get("salary"), salaryLow, salaryHigh), equals(cb, salaryJoin.get("to"), CURRENTLY_ACTIVE_ENTRY)); } private Predicate titlePredicate(Root<Employee> root, CriteriaBuilder cb) { if (isAllBlank(title)) { return null; } Join<Employee, Title> titleJoin = root.join("titles", JoinType.INNER); return cb.and( like(cb, titleJoin.get("title"), title), equals(cb, titleJoin.get("to"), CURRENTLY_ACTIVE_ENTRY)); } private Predicate equals(CriteriaBuilder cb, Path<Object> field, Object value) { return cb.equal(field, value); } private Predicate like(CriteriaBuilder cb, Path<String> field, String searchTerm) { return cb.like(cb.lower(field), "%" + searchTerm.toLowerCase() + "%"); } private Predicate between(CriteriaBuilder cb, Path<Integer> field, int min, int max) { return cb.between(field, min, max); } }
Örnek - in + greaterThanOrEqualTo + join
FilterDTO şöyle olsun
public class FilterDTO { List<UserStatus> userStatuses; Integer ageGreaterThanOrEqualTo; Double gradeGreaterThanOrEqualTo; }
Specification şöyle olsun
public static Specification<User> createSpecification(FilterDTO filter){ return userStatusesIn(filter.getUserStatusIn()) .ageGte(filter.getAgeGreaterThanOrEqualTo()) .gradeGte(filter.getGradeGreaterThanOrEqualTo()) } private static Specification<User> userStatusesIn(List<String> userStatuses) { return (root, query, builder) -> { if(CollectionUtils.isEmpty(userStatuses)){ return builder.conjunction(); } return root.get(User_.status).in(userStatuses); }; } private static Specification<User> ageGte(Integer ageGte) { return (root, query, builder) -> { if(Objects.isNull(ageGte)){ return builder.conjunction(); } return builder.greaterThanOrEqualTo(root.get(User_.age), ageGte); }; } private static Specification<User> gradeGte(Integer gradeGte) { return (root, query, builder) -> { if(Objects.isNull(gradeGte)){ return builder.conjunction(); } Join<User, EducationDetail> userEducationDetailJoin = root.join(User_.educationDetail); return builder.greaterThanOrEqualTo( userEducationDetailJoin.get(EducationDetail_.grade), gradeGte); }; }
Repository şöyle olsun. Burada N+1 Select problemi için @EntittyGraph kullanılıyor.
@Repository public interface UserRepository extends JpaRepository<User,UUID>, JpaSpecificationExecutor<User> { @EntityGraph( type = EntityGraphType.FETCH, attributePaths = { "educationDetail" } ) List<User> findAll(Specification<User> specification); }
Örnek - lessThan + like + between + equal
Repository şöyle olsun
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; @Repository public interface CommentRepository extends JpaRepository <Comment, UUID>, JpaSpecificationExecutor<Comment> { Optional<Page<Comment>> findAllByUserId(String userId, Pageable page); Page<Comment> findAll(Specification<Comment> specification, Pageable page); List<Comment> findAll(Specification<Comment> specification); Page<Comment> findAll(Pageable page); List<Comment> findAll(); }
Specification yaratan kodlar şöyle olsun. Burada her şeyi birleştiren kod conditionalSearchForUser()
@Component public class CommentSpecification { public Specification<Comment> isLongTermUser() { return (root, query, cb) -> cb.lessThan( root.get("createdDate"), LocalDate.of(2020, 01, 01) .atStartOfDay( ZoneId.of("Australia/Sydney") ) .toInstant() ); } public Specification<Comment> commentLike (String searchText) { return (root, query, cb) -> cb.like( root.get("description"), "%"+ searchText + "%" ); } public Specification<Comment> createdBetween ( Instant fromDate, Instant toDate) { return (root, query, cb) -> cb.between( root.get("createdDate"), fromDate, toDate ); } public Specification<Comment> byUser ( String userId) { return (root, query, cb) -> cb.equal( root.get("userId"), userId ); } public Specification<Comment> conditionalSearchForUser( String searchText, Instant fromDate, Instant toDate, String userId, boolean checkIsLongTermUser ) { Specification spec = null; spec = where(byUser(userId)); if(checkIsLongTermUser == true) spec = spec.and(isLongTermUser()); if(searchText!=null && !searchText.isBlank()) spec = spec.and(commentLike(searchText)); if(fromDate!= null && !fromDate.isBlank() && toDate!= null && !toDate.isBlank()) { spec = spec.and( createdBetween( LocalDate.parse(fromDate).atStartOfDay( ZoneId.of("Australia/Sydney") ) .toInstant(), LocalDate.parse(toDate).atStartOfDay( ZoneId.of("Australia/Sydney") ) .toInstant() ) ); } return spec; } }
Şöyle yaparız. Burada Specificaiton + Page kullanılıyor
commentRepository.findAll( commentSpecification.conditionalSearchForUser( searchText, fromDate, toDate, userId, checkIsLongTermUser), page);
Örnek - like + join
Specification kodlarımız şöyle olsun
static Specification<Insurance> withTripSource(@Nullable String source) {
  return (root, cq, cb) -> title == null ? null : 
    cb.like(cb.lower(root.join("trip", JoinType.LEFT).get("source")), 
            cb.lower(cb.literal("%" + source + "%")));
}
Şöyle yaparız
public interface InsuranceRepository extends JpaRepository<Insurance, UUID>,
JpaSpecificationExecutor<Insurance> {
}
Kullanmak için şöyle yaparız
Specification<Insurance> specs = Specification
        .where(withStatus(status))
        .and(withTripSource(source))
        .and(withTripDestination(destination))
        .and(withPostCompanyName(name));

Pageable paging = PageRequest.of(page, size);
return insuranceRepository.findAll(specs, paging);

26 Mayıs 2022 Perşembe

SpringBatch application.properties Ayarları

Örnek
Şöyle yaparız
spring.batch.job.enabled=false
Örnek
Şöyle yaparız
spring.batch.initialize-schema=ALWAYS
Açıklaması şöyle
If we don't use this property and start the application, the application will complain Table batch_job_instance doesn't exist.

To avoid this error, we are basically telling to create batch job-related metadata during startup. This property will create additional database tables in your database like batch_job_execution, batch_job_execution_context, batch_job_execution_params, batch_job_instance etc.