20 Ağustos 2021 Cuma

SpringCache Redis RedisCacheConfiguration Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.data.redis.cache.RedisCacheConfiguration;
disableCachingNullValues metodu
Örnek
Şöyle yaparız
@Bean
public RedisCacheConfiguration cacheConfiguration() {
  return RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofMinutes(60))
    .disableCachingNullValues()
    .serializeValuesWith(RedisSerializationContext.SerializationPair
      .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}
entryTtl metodu
Örnek
Açıklaması şöyle
By setting the expiration time of different keys in the configuration file, the effect of customizing the key expiration time can be achieved.
Şöyle yaparız
@Autowired
private MyProperties properties;
 
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
  RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration
    .defaultCacheConfig()
    .entryTtl(Duration.ofMinutes(10)).disableCachingNullValues();
 
  RedisCacheWriter redisCacheWriter = RedisCacheWriter
    .nonLockingRedisCacheWriter(connectionFactory);
 
  ImmutableSet.Builder<String> cacheNames = ImmutableSet.builder();
  ImmutableMap.Builder<String, RedisCacheConfiguration> cacheConfig = 
    ImmutableMap.builder();
  for (Map.Entry<String, Duration> entry : properties.getInitCaches().entrySet()) {
    cacheNames.add(entry.getKey());
    cacheConfig.put(entry.getKey(), defaultCacheConfig.entryTtl(entry.getValue()));
  }
 
  return RedisCacheManager.builder(redisCacheWriter)
    .cacheDefaults(defaultCacheConfig)
    .initialCacheNames(cacheNames.build())
    .withInitialCacheConfigurations(cacheConfig.build())
    .build();
}
Properties dosyası şöyledir
initCaches:
  fooboo: 10m
  fooboo1: 1h
Properties sınıfı şöyledir
@ConfigurationProperties
public class MyProperties {
 
   private final Map<String, Duration> initCaches = Maps.newHashMap();
 
   public Map<String, Duration> getInitCaches() {
     return initCaches;
   }
}
serializeKeysWith ve serializeValuesWith metodları
Örnek - JSON
Şöyle yaparız. Burada ObjetMapper parametresi ile Spring'e ait olan bean kullanılıyor. Yeni bir tane yaratmaya gerek yok
@Configuration
@EnableCaching
class CachingConfig{

  @Bean
  fun cacheConfiguration(mapper: ObjectMapper): RedisCacheConfiguration {
    return RedisCacheConfiguration.defaultCacheConfig()
      .entryTtl(Duration.ofMinutes(5))
      .disableCachingNullValues()
      .serializeValuesWith(
        SerializationPair.fromSerializer(GenericJackson2JsonRedisSerializer(mapper))
      )
  }
}
Eğer Spring'e ait olan bean'ini kopyasın alıp yeni module takmak istersek şöyle yaparız
@Bean
fun cacheConfiguration(mapper: ObjectMapper): RedisCacheConfiguration {
  val myMapper = mapper.copy()
    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
    .registerModule(
      Hibernate6Module()
        .enable(Hibernate6Module.Feature.FORCE_LAZY_LOADING)
        .enable(Hibernate6Module.Feature.REPLACE_PERSISTENT_COLLECTIONS)
      )
      .activateDefaultTyping(
        jacksonObjectMapper().polymorphicTypeValidator,
        ObjectMapper.DefaultTyping.EVERYTHING,
        JsonTypeInfo.As.PROPERTY
      )
  return RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofMinutes(5))
    .disableCachingNullValues()
    .serializeValuesWith(
      SerializationPair.fromSerializer(GenericJackson2JsonRedisSerializer(myMapper))
    )
}
Örnek - RedisSerialization
Şöyle yaparız
@EnableCaching
@Configuration
public class RedisConfig {

  private final RedisSerializer<String> stringSerializer = RedisSerializer.string();
  private final RedisSerializer<Object> objectRedisSerializer = RedisSerializer.json();

  private final Duration ttl = Duration.ofHours(6);

  @Bean
  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
redisConnectionFactory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(redisConnectionFactory);
    return redisTemplate;
  }

  @Bean
  public RedisCacheConfiguration redisCacheConfiguration() {
    return RedisCacheConfiguration
      .defaultCacheConfig()
      .serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(stringSerializer))
      .serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(objectRedisSerializer))
      .entryTtl(ttl);
  }
}

16 Ağustos 2021 Pazartesi

SpringData JpaRepository Derived Query - Not

Giriş
Belirtilen değere sahip olmayan alanlara göre nesne seçebilmemizi sağlar

Örnek
Elimizde şöyle bir kod olsun
@Entity
public class Insurance {

  ...   
  @Enumerated(EnumType.STRING)
  private Status insuranceStatus;
}
Şöyle yaparız. Böylece örneğin status alanı ARCHIVED olmayanları seçebiliriz.
public interface InsuranceRepository extends CrudRepository<Insurance, Long> {

  // Some generic CRUD methods ...
  List<Insurance> findAllByStatusNot(Insurance.Status status);
}
Şöyle yaparız
public interface InsuranceRepository extends CrudRepository<Insurance, Long> {

  // Some generic CRUD methods ...
Page<Insurance> findAllByStatusNot(Insurance.Status status, Pageable pageable);
}

10 Ağustos 2021 Salı

SpringData Jdbc Kullanımı

Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency> 
  <groupId>org.postgresql</groupId>
  <artifactId>postgresql</artifactId>
  <scope>runtime</scope>
</dependency> 
<dependency> 
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>test</scope>
</dependency> 
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>
Açıklaması şöyle
When we use Spring Boot, we don’t need to create DataSource and JdbcTemplate/NamedParameterJdbcTemplate.

We just need to provide following details in application.properties and include corresponding starter poms in pom.xml:

spring.datasource.url
spring.datasource.username
spring.datasource.password

Spring boot will automatically create DataSource and JdbcTemplate bean.
Örnek
application.properties şöyledir
#database
spring.datasource.url=jdbc:postgresql://localhost:5432/cars
spring.datasource.username=postgres
spring.datasource.password=admin
spring.datasource.driver-class-name=org.postgresql.Driver

#Queries
car.query.find.by.id=SELECT * FROM CAR WHERE ID = :id
car.query.delete.by.id=DELETE FROM CAR WHERE ID =:id
car.query.update=update CAR set name = :name, city = :city, model= :model, color =:color
  where id = :id
car.query.find.all=select * from CAR ORDER BY id LIMIT :limit OFFSET :offset
Veri tabanı cümlelerini bir sınıfa okuruz. Şöyle yaparız
@Component
public class CarQueries {

    @Value("${car.query.find.by.id}")
    private String findById;
    @Value("${car.query.delete.by.id}")
    private String deleteById;
    @Value("${car.query.update}")
    private String update;
    @Value("${car.query.find.all}")
    private String findAll;
    //...
}
Repository sınıfımız için constructor şöyledir
@Repository
public class CarDAO {

  private final NamedParameterJdbcTemplate template;
  private final CarQueries queries;
  private final RowMapper<Car> rowMapper;
  private final SimpleJdbcInsert insert;

  @Autowired
  public CarDAO(NamedParameterJdbcTemplate template, CarQueries queries) {
    this.template = template;
    //this.rowMapper = new BeanPropertyRowMapper<>(Car.class);
    this.rowMapper = new CarRowMapper();
    this.queries = queries;
    this.insert = new SimpleJdbcInsert(template.getJdbcTemplate());
    this.insert.setTableName("car");
    this.insert.usingGeneratedKeyColumns("id");
  }
}
Repository sınıfımızın metodları şöyledir
@Transactional
public Car insert(Car car) {
  //Number id = insert.executeAndReturnKey(new BeanPropertySqlParameterSource(car));
  Number id = insert.executeAndReturnKey(car.toMap());
  return findBy(id.longValue()).orElseThrow(() -> new IllegalStateException(""));
}
@Transactional
public Optional<Car> findBy(Long id) {
  String sql = queries.getFindById();
  Map<String, Object> parameters = Collections.singletonMap("id", id);
  return template.queryForStream(sql, parameters, rowMapper).findFirst();
}

@Transactional
public boolean delete(Long id) {
  String sql = queries.getDeleteById();
  Map<String, Object> paramMap = Collections.singletonMap("id", id);
  return template.update(sql, paramMap) == 1;
}

@Transactional
public boolean update(Car car) {
  String sql = queries.getUpdate();
  Map<String, Object> paramMap = car.toMap();
  return template.update(sql, paramMap) == 1;
}

@Transactional
public Stream<Car> findAll(Page page) {
  String sql = queries.getFindAll();
  Map<String, Object> paramMap = new HashMap<>();
  paramMap.put("limit", page.getLimit());
  paramMap.put("offset", page.getOffset());
  return template.queryForStream(sql, paramMap, rowMapper);
}

SpringCloud Feign Feign Sınıfı

Giriş
SpringCloud Feign projesindeki anotasyonları kullanmadan kütüphaneyi direkt kullanmak mümkün.

Örnek
Elimizde şöyle bir arayüz olsun
@Headers("Accept: application/json")
public interface BookClient {

    @RequestLine("POST")
    @Headers("Content-Type: application/json")
    void create(Book book);

    @RequestLine("GET")
    List<Book> findAll();

    @RequestLine("GET /{id}")
    Book findById(@Param("id") Integer id);

    @RequestLine("DELETE /{id}")
    void remove(@Param("id") Integer id);

    @RequestLine("PUT /{id}")
    @Headers("Content-Type: application/json")
    void update(@Param("id") Integer id, Book book);
}
Şöyle yaparız
@Slf4j
public class FeignTest {
  private static BookClient bookClient;
  private static WireMockServer wireMockServer;
  private final static String FOLDER = "src/test/resources/wiremock";

  @BeforeAll
  public static void setup() {
    bookClient = Feign.builder().client(new OkHttpClient())
      .encoder(new GsonEncoder()).decoder(new GsonDecoder())
      .logger(new Slf4jLogger(BookClient.class)).logLevel(Logger.Level.FULL)
      .target(BookClient.class, "http://localhost:57002/api/books");

    wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig()
      .withRootDirectory(FOLDER).port(57002)
      .notifier(new ConsoleNotifier(true)));
      wireMockServer.start();
  }

  @Test
  public void findById() throws Exception {
    wireMockServer.stubFor(get(urlPathEqualTo("/api/books/1"))
      .willReturn(aResponse().withBodyFile("book1.json")));

    Book book = bookClient.findById(1);
    assertThat(book.getAuthor(), equalTo("Orson S. Card"));
    wireMockServer.verify(1, getRequestedFor(urlEqualTo("/api/books/1"))
      .withHeader("Accept", WireMock.equalTo("application/json")));
  }
}

9 Ağustos 2021 Pazartesi

SpringTest MockMultipartFile Sınıfı

Örnek
Elimizde şöyle bir kod olsun
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiOperation(value = "Saves File instance.", tags = {"File"})
@ApiResponses({
        @ApiResponse(code = 201, message = "A File was successfully created.")
})
public ResponseEntity create(@RequestPart(name = "model") @Valid FileCreationDto model, 
                             @RequestPart(name = "file") MultipartFile file) {
    try {
        model.setData(file.getBytes());
    } catch (IOException e) {
        return ResponseEntity.badRequest().build();
    }

  File fileCreated = fileService.create(model, file.getOriginalFilename());
  return ResponseEntity.status(HttpStatus.CREATED).body(fileCreated);
}
Şöyle yaparız
@Test
public void create_withName_OK() throws Exception {
  
  MockMultipartFile model = ...;
  MockMultipartFile file = ...;

  MvcResult result = mvc.perform(
    MockMvcRequestBuilders.multipart("/api/files")
      .file(model)
      .file(file))
      .andDo(print())
      .andExpect(status().isCreated())
      .andReturn();

  File parsed = jsonMapper.readValue(result.getResponse().getContentAsByteArray(), 
    new TypeReference<>() {});

  ...
}


SpringData Jsr310JpaConverters Sınıfı

Giriş
Yeni Java sınıflarını, eski Date sınıflarına çevirir. Açıklaması şöyle
JPA 2.1 converters to turn JSR-310 types into legacy Dates. 
Örnek
Şöyle yaparız
@Entity
@Table(name = "file")
public class File {

  @Id
  @GeneratedValue(generator = "uuid")
  @GenericGenerator(name = "uuid", strategy = "uuid2")
  @Column(name = "id")
  private String id;

  ...

  @Convert(converter = Jsr310JpaConverters.LocalDateTimeConverter.class)
  @Column(name = "created_at")
  private LocalDateTime createdAt = LocalDateTime.now();
}

5 Ağustos 2021 Perşembe

SpringCache @Caching Anotasyonu

Açıklaması şöyle
@Caching : Regroups multiple cache operations to be applied on a method
evict Alanı
Açıklaması şöyle
@Caching: Java doesn’t allow you to use the same annotation type twice in a method or class. So, if you want to say @CacheEvict in two different caches in the same method @Cacheable can be used to aggregate other cache annotations.
Örnek
Şöyle yaparız
@Caching(evict = {
  @CacheEvict(“address”), 
  @CacheEvict(value=“employee”, key=”#employee.id”)
})
public Employee getEmployee(Employee employee) {
  // some code
}
Örnek
Şöyle yaparız. Burada aynı anda iki cache'ten birden silme işlemi olduğu için @Caching kullanılıyor
@RestController("/")
public class TodoListController {

  private ToDoList sample = new ToDoList(1, new ArrayList<ToDo>(Arrays.asList(
new ToDo(1, "Comment", false), 
    new ToDo(2, "Clap", false),
    new ToDo(3, "Follow Author", false))), false);

  @DeleteMapping("/todo/{id}")
  @Caching(evict = {
    @CacheEvict(value = "todo-single", key = "#id"),
    @CacheEvict(value="todo-list", key="'getList'")
  })
  public void deleteToDo(@PathVariable("id") long id) throws Exception {
    Optional<ToDo> item = sample.getTasks().stream()
.filter(x -> x.getId() == id).findFirst();
    if (item.isPresent()) {
      sample.getTasks().remove(item.get());
    } else {
      throw new Exception("To Do item not found");
    }
  }
}
Geri kalan kodları şöyle yaparız
@Cacheable(value = "todo-list" , key="'getList'")
@GetMapping
public ToDoList getList() {
  return sample;
}

@Cacheable(value = "todo-single", key = "#id")
@GetMapping("/todo/{id}")
public Optional<ToDo> getToDo(@PathVariable("id") long id) {
  return sample.getTasks().stream().filter(x -> x.getId() == id).findFirst();
}

@PutMapping("/todo")
@CachePut(value = "todo-single", key = "#toDo.id")
@CacheEvict(value="todo-list" , key="'getList'")
public ToDoList addToDo(@RequestBody ToDo toDo) {
  sample.getTasks().add(toDo);
  return sample;
}

@PostMapping("/todo/{id}")
@CachePut(value = "todo-single", key = "#id")
@CacheEvict(value="todo-list", key="'getList'")
public ToDo updateToDo(@PathVariable("id") long id, @RequestBody ToDo toDo)
throws Exception {
  Optional<ToDo> item = sample.getTasks().stream()
.filter(x -> x.getId() == id).findFirst();
  if (item.isPresent()) {
    item.get().setCompleted(toDo.isCompleted());
    return item.get();
  } else {
    throw new Exception("To Do item not found");
  }
}
Ve şöyle yaparız
@DeleteMapping("/todo/{id}")
@Caching(evict = {
@CacheEvict(value = "todo-single", key = "#id"),
  @CacheEvict(value="todo-list", key="'getList'")
})
public void deleteToDo(@PathVariable("id") long id) throws Exception {
  Optional<ToDo> item = sample.getTasks().stream()
.filter(x -> x.getId() == id).findFirst();
  if (item.isPresent()) {
    sample.getTasks().remove(item.get());
  } else {
    throw new Exception("To Do item not found");
  }
}