31 Temmuz 2023 Pazartesi

SpringIntegration Redis @RedisLockable Anotasyonu

Giriş
Kilidi kodla bırakmak için SpringIntegration Redis RedisLockRegistry Sınıfı yazısına bakabilirsiniz

Maven
Şu satırı dahil ederiz
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-redis</artifactId>
    <version>5.5.0</version>
</dependency>
Örnek
application.properties şöyle olsun
@Configuration
public class AppConfig {

  @Bean
  public RedisConnectionFactory redisConnectionFactory() {
    return new JedisConnectionFactory();
  }
}


@Service
public class MyService {

  @RedisLockable(key = "my-lock-key", leaseTime = 60_000, waitTime = 5_000)
  public void doSomethingWithLock() throws InterruptedException {
    // Do something with the lock
  }
}
Şöyle yaparız
@Configuration
public class AppConfig {

  @Bean
  public RedisConnectionFactory redisConnectionFactory() {
    return new JedisConnectionFactory();
  }
}


@Service
public class MyService {

  @RedisLockable(key = "my-lock-key", leaseTime = 60_000, waitTime = 5_000)
  public void doSomethingWithLock() throws InterruptedException {
    // Do something with the lock
  }
}
Açıklaması şöyle
In this example, we annotate the doSomethingWithLock method with the @RedisLockable annotation, which specifies the key to use for the lock, the lease time (in milliseconds), and the wait time (in milliseconds). The lease time determines how long the lock should be held before it is automatically released, while the wait time determines how long the method should wait to acquire the lock before giving up and throwing an exception.





26 Temmuz 2023 Çarşamba

SpringBatch MongoItemWriter Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.batch.item.data.MongoItemWriter;
Örnek
Şöyle yaparız
@RequiredArgsConstructor
@Slf4j
public class PhishDetailsWriter extends MongoItemWriter<PhishDetailsDto> {
  private final MongoTemplate mongoTemplate;

  @Override
  protected void doWrite(Chunk<? extends PhishDetailsDto> chunk) {
    log.info("Writing chunk of size: {}", chunk.size());
    BulkOperations bulkOps = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED,
      "domains");
    for (PhishDetailsDto item : chunk) {
      Query query = new Query(Criteria.where("domain").is(item.getDomain())); 
      Update update = new Update().addToSet("urls").each(item.getUrls()); 
      bulkOps.upsert(query, update);
    }
    bulkOps.execute();
  }
}

SpringBatch JsonItemReader Sınıfı - Dosya Okur

Giriş
Şu satırı dahil ederiz
import org.springframework.batch.item.json.JsonItemReader;
Örnek
Şöyle yaparız
@Bean    
public JsonItemReader<PhishtankPhishDetails> phishtankReader() {
  return new JsonItemReaderBuilder<PhishtankPhishDetails>()
    .jsonObjectReader(new JacksonJsonObjectReader<>(PhishtankPhishDetails.class))
    .resource(phishtankLinksClassPathResource)
    .name("phishDetailsJsonItemReader")
    .build();
}

SpringNative reflection-config.json Dosyası

Giriş
Açıklaması şöyle
With a clean or very small Spring Boot application it might work out of the box. However for most applications it will not work in this way because of a GraalVM reflection incompatibility.

In this case, there will be error notifications such as the following:

Warning: Could not resolve org.h2.Driver for reflection configuration. Reason: java.lang.ClassNotFoundException: org.h2.Driver.
Bir başka açıklama şöyle. Yani GraalVM sadece statik analiz ile sınıfları bulmaya çalışıyor, ama Java'da çok fazla reflection kullanıldığı için GraalVM'in tüm kodları bulma şansı yok. Bu yüzden java.lang.NoClassDefFoundError: Could not initialize class ... veya ClassNotFoundException gibi hatalar alıyoruz
When you use Native Image to build native executables it only includes the elements reachable from your application entry point, its dependent libraries, and JDK classes discovered through static analysis. However, the reachability of some elements (such as classes, methods, or fields) may not be discoverable due to Java’s dynamic features including reflection, resource access, dynamic proxies, and serialization. If an element is not reachable, it is not included in the generated executable at build time, which can lead to failures at run time. Native Image has built-in metadata for JDK classes but user code and dependencies may use dynamic features of Java that are undiscoverable by the Native Image analysis. For this reason, Native Image accepts additional reachability metadata in the form of JSON files. Since this metadata is specific to a specific code base, the JSON files providing the corresponding metadata can be shared for libraries and frameworks. This repository is a centralized place for sharing such files for libraries and frameworks that do not provide built-in metadata yet. It is also used to retrofit metadata for older versions of libraries and frameworks.

Çıktı JSON Dosyaları
reflection kullanan sınıfları reflection-config.json dosyasına yazmak gerekir. Dosyanın yolu şöyle
/src/main/resources/META-INF/native-image/reflect-config.json
Dosyayı Üretmek
1. java ... komutu ile uygulama çalıştırılır
2. Kodun çeşitli yerlerinin koşması sağlanır. Örneğin uygulamaya REST istekleri gönderiririz
3. Ctrl + C ile agent sonlandırılır. Agent çıktıyı dosyalara yazar

config-output-dir seçeneği
Normalde dosyaların src/main/resources/META-INF/native-image/ dizininde olması gerekir ama bir sebepten farklı bir hedef dizin vermek gerekirse bu seçenek kullanılır.

Örnek
Şöyle yaparız
java -agentlib:native-image-agent=config-output-dir=target/ \
  -jar native-0.0.1-SNAPSHOT.jar 
target/target dizini altında şu dosyalar üretilir
proxy-config.json
reflect-config.json
resource-config.json

config-merge-dir seçeneği
Örnek
Şöyle yaparız. config-merge-dir seçeneği ile mevcut dosyaya ekleme yapılır
java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image \
  -jar target/my-application-1.0.0-SNAPSHOT.jar
Açıklaması şöyle
This command will generate in the folder the following files:

jni-config.json
predefined-classes-config.json
proxy-config.json
reflect-config.json
resource-config.json
serialization-config.json
Açıklaması şöyle
With the generated metadata and the fixed initialization time of the classes, the native image build should be successful. Nonetheless, at runtime there could come up more errors. The most common one is the ClassNotFoundException. That means that the configuration in the reflect-config.json is incomplete and you should add the class. Another similar error is a FileNotFoundException because a file could not be located in the classpath. This means that the required file is missing in the resource-config.json.
Eğer META-INF/native-image yerine başka bir dizin kullanmak istersek şöyle yaparız
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
  <buildArgs>
    <buildArg>-H:ConfigurationResourceRoots=path/to/resources/</buildArg>
  </buildArgs>
</configuration>

Dosyanın İçin Nasıldır
Örnek
Şöyle yaparız
[
  {
    "name":"org.h2.Driver"
  }
]
Örnek
Şöyle yaparız
[
{ "name": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl", "allDeclaredConstructors":true }, { "name": "kotlin.KotlinVersion", "allPublicMethods": true, "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true }, { "name": "kotlin.KotlinVersion[]" }, { "name": "kotlin.KotlinVersion$Companion" }, { "name": "kotlin.KotlinVersion$Companion[]" }, { "name": "kotlin.internal.jdk8.JDK8PlatformImplementations", "allPublicMethods": true, "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true } ]

24 Temmuz 2023 Pazartesi

SpringMVC RestClient Sınıfı - RestTemplate Gibidir

Giriş
Açıklaması şöyle. Yani reactive ve asenkron bir şey kullanmıyorsak ve RestTemplate'a göre daha basit bir şey istiyorsak RestClient kullanılabilir.
Spring framework has offered two different options to perform http requests:

1. RestTemplate: It was introduced in Spring 3 over a decade ago. It is an implementation of the Template pattern providing synchronous blocking communication.
2. WebClient: It was released in Spring 5 as part of Spring WebFlux library. It provides a fluent API and it follows a reactive model.
RestRemplate approach exposed too many HTTP features leading to a big number of overloaded methods. It employs the one thread per request paradigm from the Jakarta Servlet API.

WebClient is the replacement for RestTemplate supporting both synchronous and asynchronous calls. It is part of the Spring Web Reactive project.

Now Spring 6.1 M1 version presents RestClient. A new synchronous http client which works in a similar way to WebClient, using the same infrastructure as RestTemplate.
constructor
Şöyle yaparız
private String encodeBasic(String username, String password) {
    return "Basic "+Base64
        .getEncoder()
        .encodeToString((username+":"+password).getBytes());
}

RestClient restClient = RestClient.builder()
    .baseUrl(properties.getUrl())
    .defaultHeader(HttpHeaders.AUTHORIZATION,
        encodeBasic(properties.getUsername(), 
                    properties.getPassword())
    ).build();
delete metodu
Örnek
Şöyle yaparız
ResponseEntity<Void> response = restClient.delete()
  .uri("/{id}",2)
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .toBodilessEntity();

logger.info("Deleted with status " + response.getStatusCode());
get metodu
Açıklaması şöyle
RestClient can also convert a response body in JSON format. Spring will automatically register by default MappingJackson2HttpMessageConverter or MappingJacksonHttpMessageConverter if Jackson 2 library or Jackson library are detected in the classpath. But you can register your own message converters and override the default settings.
Örnek
Şöyle yaparız
String data = restClient.get()
  .uri("?status={STATUS}&vip={vip}","activated", true)
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .body(String.class);

logger.info(data);

CustomerResponse customer = restClient.get()
  .uri("/{id}",3)
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .body(CustomerResponse.class);

logger.info("Customer name: " + customer.personInfo().name());

List<CustomerResponse> customers = restClient.get()
  .uri("?status={STATUS}&vip={vip}","activated", true)    
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .body(List.class);

logger.info("Customers size " + customers.size());
exchange metodu
Açıklaması şöyle
The exchange method is useful for situations where the response must be decoded differently depending on the response status. Status handlers are ignored when the exchange method is employed.
Örnek
Şöyle yaparız
SimpleResponse simpleResponse = restClient.get()
  .uri("/{id}",4)
  .accept(MediaType.APPLICATION_JSON)
  .exchange((req,res) -> 
    switch (res.getStatusCode().value()) {
      case 200 -> SimpleResponse.FOUND;
      case 404 -> SimpleResponse.NOT_FOUND;
      default -> SimpleResponse.ERROR;
    }
);
post metodu
Örnek
Şöyle yaparız
ResponseEntity<Void> response = restClient.post()
                .accept(MediaType.APPLICATION_JSON)
                .body(customer)
                .retrieve()
                .toBodilessEntity();

if (response.getStatusCode().is2xxSuccessful()) {
  logger.info("Created " + response.getStatusCode());
  logger.info("New URL " + response.getHeaders().getLocation());
}
Handling Errors
Açıklaması şöyle
What happens if we try to delete or retrieve a non-existing customer? The customer endpoint will return a 404 error code along with a message details. However, RestClient will throw a subclass of RestClientException whenever a client error status (400-499) or server error status (500-599) are received.

To define our custom exception handlers there are two options that work at different levels:

1. In the RestClient with defaultStatusHandler method (for all http request sent with it)
2. For each http request with the onstatus method after the call to retreive method (this method returns a ResponseSpec interface).
defaultStatusHandler metodu
Örnek
Şöyle yaparız
RestClient restClient = RestClient.builder()
  .baseUrl(properties.getUrl())
  .defaultHeader(HttpHeaders.AUTHORIZATION,
                 encodeBasic(properties.getUsername(), 
                 properties.getPassword()))
  .defaultStatusHandler(
    HttpStatusCode::is4xxClientError,
    (request, response) -> {
      logger.error("Client Error Status " + 
      response.getStatusCode());
      logger.error("Client Error Body "+new 
      String(response.getBody().readAllBytes()));
  })
.build();
onStatus metodu
Örnek
Şöyle yaparız
ResponseEntity response = restClient.delete()
  .uri("/{id}",2)
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError,
    (req, res) -> 
    logger.error("Couldn't delete "+res.getStatusText())
  )
  .toBodilessEntity();

  if (response.getStatusCode().is2xxSuccessful())
    logger.info("Deleted with status " + response.getStatusCode());



20 Temmuz 2023 Perşembe

PropertiesLauncher Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.boot.loader.PropertiesLauncher;
loader.path Alanı
Açıklaması şöyle
a comma-separated list of directories (containing file resources and/or nested archives in *.jar or *.zip or archives) or archives to append to the classpath. BOOT-INF/classes,BOOT-INF/lib in the application archive are always used
Örnek
Şöyle yaparız. Eğer META-INF/MANIFEST.MF dosyasında 
Main-Class: org.springframework.boot.loader.PropertiesLauncher yazmıyorsa şöyle yaparız
java -cp target/myapp-1.0-SNAPSHOT.jar -Dloader.path=/home/user/extlib \
  org.springframework.boot.loader.PropertiesLauncher
Yazıyorsa şöyle yaparız
java -Dloader.path=/home/user/extlib-jar target/myapp-1.0-SNAPSHOT.jar

18 Temmuz 2023 Salı

SpringData Rest findAll İşlemi

Giriş
findAll işlemi için repository ismi yazılır. 

Örnek
Şöyle yaparız
http://localhost:8080/people  
Örnek
Elimizde şöyle bir kod olsun
@Entity
@Table(name="tb_Student")
public class Student {
  ...
}

public interface StudentRepo extends JpaRepository<Student, Integer>{
}
İstek gönderelim. URL'de repository'nin çoğul hali kullanılıyor
http://localhost:8080/students
Çıktı şöyle
{
  "_embedded" : {
    "students" : [ {
      "firstName" : "Avans",
      "lastName" : "MA",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/students/1"
        },
        "student" : {
          "href" : "http://localhost:8080/api/students/1"
        }
      }
    }, {
      "firstName" : "Abhi",
      "lastName" : "WE",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/students/2"
        },
        "student" : {
          "href" : "http://localhost:8080/api/students/2"
        }
      }
    }, 
    ...
   ]
}

17 Temmuz 2023 Pazartesi

SpringBoot Test @DataMongoTest Anotasyonu

Örnek
Şöyle yaparız
@Testcontainers
@DataMongoTest(excludeAutoConfiguration = MongoAutoConfiguration.class)
public class MongoTestEnvironment {
  public static MongoDBContainer mongoDBContainer = new MongoDBContainer(
    DockerImageName.parse(PropertyFileLoader
      .getProperty("mongo.db.docker.image"))).withReuse(true);
}

public class TestEnvironment extends MongoTestEnvironment {
  ...
}

SpringContext ApplicationReadyEvent Sınıfı

Giriş
Şu satırı dahil ederiz.
import org.springframework.boot.context.event.ApplicationReadyEvent;
Açıklaması şöyle
Event published as late as conceivably possible to indicate that the application is ready to service requests. The source of the event is the SpringApplication itself, but beware of modifying its internal state since all initialization steps will have been completed by then.
Örnek
Şöyle yaparız. Burada temizlik işi yaptıktan sonra uygulama kapatılıyor. Bu tazr şeyler Kubertenes Cronjob ile kullanılabilir
@Component
public class RunAfterStartup {
   
  @Autowired
  private BusinessService businessService;
    
  @Autowired
  private ApplicationContext context;

  @EventListener(ApplicationReadyEvent.class)
  public void runAfterStartup() {
    businessService.performCleanup();
    shutdown();
  }

  private void shutdown() {
    int exitCode = SpringApplication.exit(context, () -> 0);
    System.exit(exitCode);
  }
}

SpringData Jdbc JdbcTemplate.query metodu - SQL + RowMapper

Giriş
En kolay ve nen çok kullanılan metodlardan birisi bu. Girdi olarak bir sql cümlesi ve RowMapper nesnesi alır ve List döner. Basit Select cümleler içindir. 

- RowMapper ne tipten bir nesne döndürüleceğini belirtir. Eğer özel bir sonuç nesnesi kodlamak istemiyorsak RowMapper Map<String,String> bile döndürebilir.

Örnek
Şöyle yaparız. Burada RowMapper bir User nesnesi döndürüyor.
List<User> users = jdbcTemplate.query("SELECT * FROM USER", new UserRowMapper());
Örnek
Şöyle yaparız.. Burada RowMapper bir Foo nesnesi döndürüyor.
public List<Foo> getAgateCounts(){
  String sql = "select counts, flag, feed_id from cd_product_reg_stage";
  return jdbcTemplate.query(sql, new InnerMapper());
}
static final class InnerMapper implements RowMapper<Foo> { 
  @Override
  public Foo mapRow(ResultSet rs,int rowNumber) throws SQLException{
    Foo obj = new Foo();
    obj.setCounts(rs.getLong("counts"));
    obj.setProcessed_flag(rs.getString("flag"));
    obj.setFeed_id(rs.getLong("feed_id")); 
    return obj;
  }
}
Örnek
Şöyle yaparız. Burada RowMapper bir Student nesnesi döndürüyor.
String SELECT_ALL_STUDENTS = "SELECT Student_id, Name, Address, Info FROM Students";

public List<Student> getAllStudents(){
  
  return jdbcTemplate.query(SELECT_ALL_STUDENTS, new RowMapper<Student>() { 
    @Override
public Student mapRow(ResultSet resultSet, int rowNumber) throws SQLException { int studentId = resultSet.getInt("Student_id "); String studentName = resultSet.getString("Name"); String studentAddress= resultSet.getString("Address"); String studentInfo= resultSet.getString("Info"); return new Student(studentId, studentName, studentAddress, studentInfo); } }); }
Örnek
Şöyle yaparız. Burada RowMapper olarak lambda kullanılıyor bir Tenant  nesnesi döndürüyor.
String query = "SELECT name, id FROM tenant";

List<Tenant> tenants = jdbcTemplate.query(query, (resultSet, rowNumber) -> {
  UUID id = UUID.fromString(resultSet.getString("id"));
  String name = resultSet.getString("name");
  return new Tenant(id, name);
});