31 Ekim 2022 Pazartesi

IntelliJ Idea - Editor Kısayolları

Edit/Find/Select All Occurrences
Multiple selections içindir. Windows'ta Ctrl + Alt + Shift + J
Bir açıklama burada

27 Ekim 2022 Perşembe

Lambda ve Checked Exception

Giriş
Açıklaması şöyle. Yani lambda içinde checked exception fırlatılamz. Checked Excepiton'ları RuntimeException'a çevirerek durum kurtarılabiliyor.
...it is not possible to call a method that throws a checked exception from a lambda directly.
Örnek
Elimizde şöyle bir kod olsun. Bu kod pek okunaklı değil.
Stream.of("...", "...", "...")
  .map(it -> {
    try {
      return Class.forName(it);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  })
  .forEach(System.out::println);
1. Lombok SneakyThrows şu anda lambda için kullanılamıyor. 
2. Apache Commons Failable kullanılabilir.
3. Vavr kullanılabilir.
4. Bu kodu şu hale çevirebiliriz
var forNamer = new ForNamer()
Stream.of("...", "...", "...")
  .map(forNamer::apply)
  .forEach(System.out::println);

record ForNamer() implements Function<String, Class<?>> {
  @Override
  public Class<?> apply(String string) {
    try {
      return Class.forName(string);
    } catch (ClassNotFoundException e) {
      return null;
    }
  }
}


Vavr Kullanımı

Maven
Şu satırı dahil ederiz
<dependency>
   <groupId>io.vavr</groupId>
   <artifactId>vavr</artifactId>
   <version>0.10.4</version>
 </dependency>
Örnek
Şöyle yaparız
var result = Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
  .map(CheckedFunction1.liftTry(Class::forName))
  .map(Try::toEither)
       .partition(Either::isLeft)                              // 1
        .map1(left -> left.map(Either::getLeft))                // 2
        .map2(right -> right.map(Either::get));                 // 3
result._1().forEach(
  it -> System.out.println("not found: " + it.getMessage()));   // 4
result._2().forEach(
  it -> System.out.println("class: " + it.getName()));          // 4
Açıklaması şöyle
1. Partition the Stream of Either in a tuple of two Stream
2. Flatten the left stream from a Stream of Either to a Stream of Throwable
3. Flatten the right stream from a Stream of Either to a Stream of Class
4. Do whatever we want


25 Ekim 2022 Salı

IntelliJ Idea Syntax Highlighting

Giriş
İlk defa burada gördüm. Settings > Editor > Color Scheme > Java menüsüne gittim

Renkli Comment
Açıklaması şöyle
Here is how you do it, Preferences -> Editor -> Color Scheme -> Java -> Comments -> (Block Comment + Line Comment) -> Foreground color (#FFB206).

Rainbow Brackets
Bir örnek burada

Custom Renklendirm
Dark Theme kullanıyordum. Metod'lar renklendirmedim, ama sınıfları renklendirmek büyük projelerde çok faydalı

Interface ve Abstract Sınıflar
Abstract class ve interface'leri şöyle yaptım. Interface'ler açık mavi oldu. Abstract sınıflar kestane rengi oldu ve altı gök mavi olarak çizildi
<scheme name="Orcun Darcula" version="142" parent_scheme="Darcula">
  <metaInfo>
    <property name="created">2022-10-25T10:28:42</property>
    <property name="ide">Idea</property>
    <property name="ideVersion">2022.2.0.0</property>
    <property name="modified">2022-10-25T10:28:47</property>
    <property name="originalScheme">Orcun Darcula</property>
  </metaInfo>
  <attributes>
    <option name="ABSTRACT_CLASS_NAME_ATTRIBUTES">
      <value>
        <option name="FOREGROUND" value="aa0000" />
        <option name="EFFECT_COLOR" value="aaaa" />
        <option name="EFFECT_TYPE" value="1" />
      </value>
    </option>
    <option name="INTERFACE_NAME_ATTRIBUTES">
      <value>
        <option name="FOREGROUND" value="8080" />
      </value>
    </option>
  </attributes>
</scheme>

19 Ekim 2022 Çarşamba

IllegalMonitorStateException Sınıfı

Giriş
Şu satırı dahil ederiz.
import java.lang.IllegalMonitorStateException;
Açıklaması şöyle
If you use wait and notify outside the synchronized block or method then it results in IllegalMonitorStateException

14 Ekim 2022 Cuma

Debezium Kullanımı

Giriş
Embedded bir engine yaratmak için DebeziumEngine nesnesi yaratılır ve bu nesne bir ExecutorService ile çalıştırılır. DebeziumEngine.notifying() ile de bir listener takılır

Propertyler
database.hostname
database.port
database.user
database.password
database.server.name
table.include.list
connector.class

Örnek - MySQL
Şöyle yaparız
// Define the configuration for the Debezium Engine with MySQL connector...
Properties props = config.asProperties(); props.setProperty("name", "engine"); props.setProperty("offset.storage", "org.apache.kafka.connect.storage.FileOffsetBackingStore"); props.setProperty("offset.storage.file.filename", "/tmp/offsets.dat"); props.setProperty("offset.flush.interval.ms", "60000"); /* begin connector properties */ props.setProperty("database.hostname", "localhost"); props.setProperty("database.port", "3306"); props.setProperty("database.user", "mysqluser"); props.setProperty("database.password", "mysqlpw"); props.setProperty("database.server.id", "85744"); props.setProperty("database.server.name", "my-app-connector"); props.setProperty("database.history", "io.debezium.relational.history.FileDatabaseHistory"); props.setProperty("database.history.file.filename", "/path/to/storage/dbhistory.dat"); // Create the engine with this configuration ... try (DebeziumEngine<ChangeEvent<String, String>> engine = DebeziumEngine.create(Json.class) .using(props) .notifying(record -> { System.out.println(record); }).build() ) { // Run the engine asynchronously ... ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(engine); // Do something else or wait for a signal or an event } // Engine is stopped when the main code is finished
Örnek - PostgreSQL
Şöyle yaparız
@Component
public class DebeziumConnector {

  private static final String SCHEMA_PLACEHOLDER = "<schema>";

  private final Logger log = LoggerFactory.getLogger(this.getClass());

  private ExecutorService executor = Executors.newSingleThreadExecutor();

  private DebeziumEngine<ChangeEvent<String, String>> engine;

  private String databaseHostname;

  private String databaseSchema;

  private String offsetFlushIntervalMs;

  private String heartBeatMs;

  private String pollIntervalMs;
  

  @PreDestroy
  public void shutdown() throws IOException {
    log.info("Shutting down the CDC engine");
    log.info("Please wait while the current position in the offset log is stored!");
    engine.close();
    executor.shutdown();
  }

  @PostConstruct
  public void start() {
    log.info("Starting the CDC engine");

    var properties = getDebeziumProperties();
    var offsetPolicy = offsetFlushIntervalMs.equals("0") ? 
      OffsetCommitPolicy.always() : OffsetCommitPolicy.periodic(properties);

    this.engine = DebeziumEngine.create(Json.class)
      .using(properties)
      .using(offsetPolicy)
      .notifying(eventHandler)
      .build();

    executor.execute(engine);
  }
}
Properties şöyle olsun
private Properties getDebeziumProperties() {
  var props = new Properties();
  var snapshotTables = "<schema>.table_one,<schema>.table_two,<schema>.table_three";

  var allTables = snapshotTables + ",<schema>.table_without_snapshot_need";

  if (!heartBeatMs.isBlank() && !heartBeatMs.equals("0")) {
    allTables = allTables + ",<schema>.cdc_heartbeat";
    props.setProperty("heartbeat.interval.ms", heartBeatMs);
    props.setProperty(
      "heartbeat.action.query",
      "insert into <schema>.cdc_heartbeat VALUES (B'1')"
          .replace(SCHEMA_PLACEHOLDER, databaseSchema)
    );
  }

  props.setProperty("name", "FooToBarCDC");
  props.setProperty("plugin.name", "pgoutput");
  props.setProperty("publication.autocreate.mode", "filtered");
  setOptionalProperty(s -> props.setProperty("offset.storage", s), offsetStorage);
  setOptionalProperty(s -> props.setProperty("offset.flush.interval.ms", s),
      offsetFlushIntervalMs);
  setOptionalProperty(s -> props.setProperty("snapshot.mode", s), snapshotMode);
  setOptionalProperty(s -> props.setProperty("poll.interval.ms", s), pollIntervalMs);

  if (FileOffsetBackingStore.class.getName().equals(offsetStorage)) {
      props.setProperty("offset.storage.file.filename", offsetStorageFileFilename);
  }

  props.setProperty("database.hostname", databaseHostname);
  props.setProperty("database.port", databasePort);
  props.setProperty("database.user", databaseUser);
  props.setProperty("database.password", databasePassword);
  props.setProperty("database.dbname", databaseDbname);
  props.setProperty("database.server.name", "foo");
  props.setProperty("table.include.list", allTables
      .replace(SCHEMA_PLACEHOLDER, databaseSchema));
  props.setProperty("snapshot.include.collection.list", snapshotTables
      .replace(SCHEMA_PLACEHOLDER, databaseSchema));
  props.setProperty("connector.class", 
      "io.debezium.connector.postgresql.PostgresConnector");        

  return props;
}

private void setOptionalProperty(Consumer<String> consumer, String property) {
  if (property != null && !property.isBlank()) {
    consumer.accept(property);
  }
}
Okunan JSON verisini çevireceğimiz sınıf şöyle olsun
public class CdcEvent {

  private long id;

  private String table;

}
Event handler şöyle olsun
@Component
public class SingleEventHandler implements 
  Consumer<ChangeEvent<String, String>> {

  private static final JsonPointer TABLE = JsonPointer.compile("/payload/source/table");

  private static final JsonPointer PAYLOAD = JsonPointer.compile("/payload/after/id");

  private ObjectMapper objectMapper = JacksonUtil.createObjectMapper();

  private Map<String, DataExtractor> dataExtractors;

  @Override
  public void accept(final ChangeEvent<String, String> event) {
    var cdcEvent = parseEvent(event);
    ...
  }
   

  private CdcEvent parseEvent(final ChangeEvent<String, String> event) {
    if (event == null || event.value() == null) {
      return new CdcEvent();
    }

    try {
      var json = objectMapper.readTree(event.value());
      var cdcEvent = new CdcEvent().setId(json.at(PAYLOAD).asLong())
                                   .setTable(json.at(TABLE).asText());
      return cdcEvent;
    } catch (JsonProcessingException e) {
      throw new EventParsingException(e);
    }
  }
}




CompletionException Sınıfı

Giriş
Şu satırı dahil ederiz
import java.util.concurrent.CompletionException;
- Bir CompletableFuture'a join() yaparsak ve CompletableFuture exception ile biterse, CompletionException fırlatılır.
- Bir CompletableFuture'a get() yaparsak ve CompletableFuture exception ile biterse, ExecutionException  fırlatılır.

Örnek - ArithmeticException
Şöyle yaparız
CompletableFuture<Void> completableFuture = CompletableFuture
  .supplyAsync(() -> {
    System.out.println("running task");
    return 1 / 0;
  })
  .thenApply(input -> {
    System.out.println("multiplying by 2");
    return input * 2;
  })
  .thenAccept(System.out::println);

  Thread.sleep(3000);//let the stages complete
  System.out.println("-- checking exceptions --");
  try {
    completableFuture.join();
  } catch (CompletionException exception) {
    exception.printStackTrace();
  }
  //compile error
  //} catch (ExecutionException exception) {
  //    exception.printStackTrace();
  //}
printStackTrace() çıktısı şöyle. "Caused by" kısmında exception'ının hangi satırdan geldiği gösteriliyor.
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
  at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
  at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
  at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1606)
  at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java)
  at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1596)
  at java.util.concurrent.ForkJoinTask.doExec$$$capture(ForkJoinTask.java:289)
  at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java)
  at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
  at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
 at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:175)
Caused by: java.lang.ArithmeticException: / by zero
  at Scratch.lambda$join$0(scratch.java:13)
  at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1604)
  ... 7 more
Örnek - completeExceptionally
Şöyle yaparız
CompletableFuture<Double> completableFuture = CompletableFuture
  .supplyAsync(() -> {
    System.out.println("running task");
    return 2.0;
  });


completableFuture.completeExceptionally(new RuntimeException("my error"));


Thread.sleep(3000);//let the stages complete
System.out.println("-- checking exceptions --");
try {
  completableFuture.join();
} catch (CompletionException exception) {
   exception.printStackTrace();
}
printStackTrace() çıktısı şöyle. "Caused by" kısmında exception'ının hangi satırdan geldiği gösteriliyor.
java.util.concurrent.CompletionException: java.lang.RuntimeException: my error
  at java.util.concurrent.CompletableFuture.reportJoin(CompletableFuture.java:375)
  at java.util.concurrent.CompletableFuture.join(CompletableFuture.java:1947)
  at Scratch.join2(scratch_6.java:49)
  at Scratch.main(scratch_6.java:83)
Caused by: java.lang.RuntimeException: my error
  at Scratch.join2(scratch.java:43)
  ... 1 more



12 Ekim 2022 Çarşamba

ArchUnit Kullanımı

Giriş
ArchUnit unit test gibi yazılarak projedeki tüm sınıfları dolaşıyor ve belli kurallara uyup uymadıklarını test ediyor.

Maven
Eğer JUnit anotasyonlarını (@ArchTest) gibi kullanmak istersek bu bağımlılıkları eklemek gerekir. Eğer bu anotasyonlara gerek yoksa sadece Core bağımlılık yeterli

Örnek
Şöyle yaparız
// if we are using JUnit 4
<dependency>
  <groupId>com.tngtech.archunit</groupId>
  <artifactId>archunit-junit4</artifactId>
  <version>0.14.1</version>
  <scope>test</scope>
</dependency>

// if we are using JUnit 5:
<dependency>
  <groupId>com.tngtech.archunit</groupId>
  <artifactId>archunit-junit5</artifactId>
  <version>0.14.1</version>
  <scope>test</scope>
</dependency>
Örnek - Core
Şöyle yaparız
<dependency>
  <groupId>com.tngtech.archunit</groupId>
  <artifactId>archunit</artifactId>
  <version>0.23.1</version>
  <scope>test</scope>
</dependency>
Gradle
Şöyle yaparız
testImplementation 'com.tngtech.archunit:archunit:0.16.0'
Kullanım
Açıklaması şöyle
An arch unit test is made up of a few common parts:

- Target the classes you want to analyze. This can be done with something like new ClassFileImporter().importPackagesOf() if you are working outside of JUnit. Or using the annotation AnalyzeClasses such as @AnalyzeClasses(packages="com.example.application") if you are working in JUnit. This gathering of classes can and should only happen once per test class as it can be an expensive, time-consuming process.

- Then the actual tests can be performed using the ArchUnit DSL.
Yani tüm kullanım şöyle
JavaClasses classes = ...
ArchRule rule = ...
rule.check(classes);

ArchRule Sınıfı
Bu sınıfı yaratmak için
1. Hazır kurallar kullanılabilir
2. Slice API kullanılabilir

Hazır Kurallar
Örnek
Şöyle yaparız
@AnalyzeClasses(packages = "com.tngtech.archunit.example.layers")
public class CodingRulesTest {

  @ArchTest
  private final ArchRule no_generic_exceptions = 
    NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS;
}
classes metodu
Örnek - Paketteki Sınıflar ve Anotasyon
Şöyle yaparız Burada belirli bir paketteki sınıfların @Entity anotasyonuna sahip olduğu kontrol ediliyor
@ArchTest
static final ArchRule entities_must_reside_in_a_model_package =
  classes().that().areAnnotatedWith(Entity.class)
    .should().resideInAPackage("..model..")
    .as("Entities should reside in a package '..model..'")
    .allowEmptyShould(true);
Benzer bir kural şöyle. Burada belirli bir paketteki sınıfların @Configuration anotasyonuna sahip olduğu kontrol ediliyor
@ArchTest
static final ArchRule configs_must_reside_in_a_config_package =
  classes().that().areAnnotatedWith(Configuration.class)
    .or().areNotNestedClasses().and().areAnnotatedWith(ConfigurationProperties.class)
    .should().resideInAPackage("..config..")
    .as("Configs should reside in a package '..config..'");
Örnek - Anotasyon ve Sınıf İsmi
Şöyle yaparız. Burada SpringBootApplication anotasyonuna sahip sınıfların isminin MainApp olması kontrol ediliyor.
@AnalyzeClasses(packages = "org.mypackage")

@ArchTest
static ArchRule app_class_name_should_be_app =
   classes().that().areAnnotatedWith(SpringBootApplication.class)
     .should().haveSimpleName("MainApp");
Örnek - Sınıf İsmi Son Ek
Şöyle yaparız Burada belirli bir paketteki sınıfların son ekinin Controller olması kontrol ediliyor. allowEmptyShould(true) ile eğer Controller sınıfı yoksa test başarısız olmuyor
@ArchTest
static ArchRule controllers_should_be_suffixed =
  classes()
    .that().resideInAPackage("..controller..")
    .or().areAnnotatedWith(RestController.class)
    .should().haveSimpleNameEndingWith("Controller")
    .allowEmptyShould(true);
fields metodu
Örnek
Şöyle yaparız
@ArchTest
private final ArchRule loggers_should_be_private_static_final =
  fields().that().haveRawType(Logger.class)
    .should().bePrivate()
    .andShould().beStatic()
    .andShould().beFinal()
    .because("we agreed on this convention");
noClasses metodu
Örnek
Şöyle yaparız
@ArchTest
static final ArchRule interfaces_should_not_have_names_ending_with_the_word_interface =
  noClasses()
  .that()
  .areInterfaces()
  .should()
  .haveNameMatching(".*Interface");
slices metodu
Örnek
Şöyle yaparız
@ArchTest
static final ArchRule no_cycles_by_method_calls_between_slices =
  slices()
  .matching("..(simplecycle).(*)..")
  .namingSlices("$2 of $1")
  .should()
  .beFreeOfCycles();
Açıklaması şöyle
This takes your code and slices it by package so the above is slicing the code by the packages directly below the simplecycle package and asserts that there are no cycles. If instead of the (*) we had (**) it would look at all sub-packages below simplecycle .
Layered Architecture Enforcement

Kendi Kuralımız
Bir örnek burada. Bu kurulu kullanmak için kuralın check() metodu çağrılır. Şöyle yaparız
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

public class KinesisSerializableTest {

  @Test
  public void serializable_classes_should_have_valid_serialVersionUID() {
    String basePackage = KinesisSinks.class.getPackage().getName();
    JavaClasses classes = new ClassFileImporter()
                .withImportOption(onlyCurrentModule())
                .importPackages(basePackage);

    ArchUnitRules.SERIALIZABLE_SHOULD_HAVE_VALID_SERIAL_VERSION_UID.check(classes);
  }
}
@AnalyzeClasses Anotasyonu
importOptions Alanı
Örnek
Şöyle yaparız
// ignore tests
@AnalyzeClasses(packages = "org.mypackage", 
  importOptions = ImportOption.DoNotIncludeTests.class)

// ignore class
@AnalyzeClasses(packages = "org.mypackage", 
  importOptions = {ArchUnitTest.ExcludeControllerImportOption.class, 
                   ImportOption.DoNotIncludeTests.class})

public class ArchUnitTest {

  static class ExcludeControllerImportOption 
    implements com.tngtech.archunit.core.importer.ImportOption {
    @Override
    public boolean includes(Location location) {
      return !location.contains("SomeControllerClassThatNeedsToBeExcluded");
    }
}

Hexagonal Architecture
Örnek
Şöyle yaparız
@ArchTest
static final ArchRule onion_architecture_is_respected = onionArchitecture()
  .domainModels("..domain.model..")
  .domainServices("..domain.service..")
  .applicationServices("..application..")
  .adapter("cli", "..adapter.cli..")
  .adapter("persistence", "..adapter.persistence..")
  .adapter("rest", "..adapter.rest..");
Örnek
Kullanımı şöyle. Kaynak kod burada
HexagonalArchitecture.boundedContext("io.reflectoring.buckpal.account")
                     .withDomainLayer("domain")
                     .withAdaptersLayer("adapter")
                     .incoming("in.web")
                     .outgoing("out.persistence")
                     .and()
                         .withApplicationLayer("application")
                         .services("service")
                         .incomingPorts("port.in")
                         .outgoingPorts("port.out")
                     .and()
                         .withConfiguration("configuration")
                         .check(new ClassFileImporter()
                         .importPackages("io.reflectoring.buckpal.."));



11 Ekim 2022 Salı

Liquibase Kullanımı

Giriş
Liquibase daha çok SpringBoot ile kullanılıyor ancak tek başına kullanım da mümkün. Bir örnek burada

Gradle
Şöyle yaparız
plugins {
id 'org.liquibase.gradle' version '2.1.1' } dependencies { liquibaseRuntime group: 'org.liquibase', name: 'liquibase-core', version: '4.2.2' liquibaseRuntime group: 'com.microsoft.sqlserver', name: 'mssql-jdbc', version: '10.2.0.jre11' }
build.gradle dosyasında şöyle yaparız
liquibase {
  activities {
    def liquibaseUrl = project.hasProperty('liquibaseUrl') ? liquibaseUrl : null
    def liquibaseUsername = project.hasProperty('liquibaseUsername') ? liquibaseUsername : null
    def liquibasePassword = project.hasProperty('liquibasePassword') ? liquibasePassword : null
    main {
      changeLogFile 'change-log.sql'
      url liquibaseUrl
      username liquibaseUsername
      password liquibasePassword
    }
  }
}
Çalıştırmak için şöyle yaparız
./gradlew update -PliquibaseUrl="<jdbc-connection-string>" -PliquibaseUsername="<jdbc-username>" -PliquibasePassword="<jdbc-password>"

ChangeLog Nedir?
Açıklaması şöyle. Yani changeset dosyalarını içeren ana dosyadır
A Liquibase changelog is typically a file that contains a list of changesets. A changeset is a group of database changes that are intended to be applied together as a unit. Each changeset includes the details of the changes to be made, such as the SQL statements to be executed or the database objects to be created or modified.
ChangeLog şu formatlarda olabilir
XML
SQL
JSON
YAML
ChangeSet Nedir?
Açıklaması şöyle. Liquibase hangi changeset'in uygulandığını takip eder. Dolayısıyla changeset geri sarılabilir.
... liquibase allows developers to define their database changes in a database-agnostic format, so that the exact changelogs can be used to update different database types, such as MySQL, Oracle, or PostgreSQL. This makes it easier to support multiple database platforms and automate the process of applying changes to a database.

XML
Örnek
Şöyle yaparız. Burada bir tablo yaratılıyor ve satır ekleniyor
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd"> <changeSet id="create-table" author="liquibase"> <createTable tableName="my_table"> <column name="id" type="int" autoIncrement="true"/> <column name="name" type="varchar(255)"/> <column name="description" type="varchar(255)"/> </createTable> </changeSet> <changeSet id="insert-row" author="liquibase"> <insert tableName="my_table"> <column name="name" value="Example name"/> <column name="description" value="Example description"/> </insert> </changeSet> </databaseChangeLog>
Örnek
changeSet tag içinde yapılabilece tablo işlemleri şöyle
<createTable tableName="customers">
  <column name="id" type="int" autoIncrement="true">
    <constraints primaryKey="true" nullable="false"/>
  </column>
  <column name="name" type="varchar(255)"/>
  <column name="email" type="varchar(255)"/>
  <column name="created_at" type="timestamp" defaultValueComputed="CURRENT_TIMESTAMP"/>
</createTable>

<addColumn tableName="customers">
  <column name="address" type="varchar(255)"/>
</addColumn>

<modifyDataType tableName="customers" columnName="name" newDataType="varchar(100)"/>

<insert tableName="customers">
  <column name="name" value="John Smith"/>
  <column name="email" value="john@example.com"/>
</insert>

<update tableName="customers" where="email = 'john@example.com'">
  <column name="name" value="John Doe"/>
</update>

<delete tableName="customers" where="email = 'john@example.com'"/>
changeSet tag içinde yapılabilecek view işlemleri şöyle
<createView viewName="customer_names">
  SELECT name FROM customers;
</createView>

<dropView viewName="customer_names"/>
changeSet tag içinde yapılabilecek stored procedure ve trigger işlemleri şöyle
<createProcedure procedureName="get_customer_by_id">
  CREATE PROCEDURE get_customer_by_id(IN customer_id INT)
  BEGIN
    SELECT * FROM customers WHERE id = customer_id;
  END
</createProcedure>

<dropProcedure procedureName="get_customer_by_id"/>

<createTrigger triggerName="customer_insert_trigger" beforeInsert="true"
  tableName="customers">

SQL
Örnek
Şöyle yaparız
--liquibase formatted sql

--changeset user1:id1
CREATE TABLE Car (
id INT PRIMARY KEY,
make VARCHAR(100)
);
Örnek
Şöyle yaparız. Burada bir tablo yaratılıyor ve satır ekleniyor
--liquibase formatted sql
--changeset liquibase:create-table
CREATE TABLE my_table (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255),
  description VARCHAR(255)
);

--changeset liquibase:insert-row
INSERT INTO my_table (name, description)
VALUES ('Example name', 'Example description');
JSON
Örnek
Şöyle yaparız. Burada bir tablo yaratılıyor ve satır ekleniyor
{
  "databaseChangeLog": {
    "changeSet": [
      {
        "id": "create-table",
        "author": "liquibase",
        "createTable": {
          "tableName": "my_table",
          "column": [
            {
              "name": "id",
              "type": "int",
              "autoIncrement": true
            },
            {
              "name": "name",
              "type": "varchar(255)"
            },
            {
              "name": "description",
              "type": "varchar(255)"
            }
          ]
        }
      },
      {
        "id": "insert-row",
        "author": "liquibase",
        "insert": {
          "tableName": "my_table",
          "column": [
            {
              "name": "name",
              "value": "Example name"
            },
            {
              "name": "description",
              "value": "Example description"
            }
          ]
        }
      }
    ]
  }
}
YAML
Örnek
Şöyle yaparız. Burada bir tablo yaratılıyor ve satır ekleniyor
databaseChangeLog:
  changeSet:
  - id: create-table
    author: liquibase
    createTable:
      tableName: my_table
      column:
      - name: id
        type: int
        autoIncrement: true
      - name: name
        type: varchar(255)
      - name: description
        type: varchar(255)
  - id: insert-row
    author: liquibase
    insert:
      tableName: my_table
      column:
      - name: name
        value: Example name
      - name: description
        value: Example description

10 Ekim 2022 Pazartesi

Mockito @Captor Anotasyonu

Giriş
Şu satırı dahil ederiz
import org.mockito.Captor;
Açıklaması şöyle
It allows the creation of a field-level argument captor. It is used with the Mockito’s verify() method to get the values passed when a method is called

9 Ekim 2022 Pazar

Datafaker

Giriş
Birim testleri için test verisi üreten iki kütüphane var.
1. Datafaker

Maven
Örnek
Şu satırı dahil ederiz
<dependency>
  <groupId>net.datafaker</groupId>
  <artifactId>datafaker</artifactId>
  <version>1.6.0</version>
</dependency>
Örnek
Şu satırı dahil ederiz. Datafaker 2.0 sürümü sadece Java 17  ve üstü ile çalışıyor
<dependency>
  <groupId>net.datafaker</groupId>
  <artifactId>datafaker</artifactId>
  <version>2.0.1</version>
</dependency>
Faker Sınıfı
collection metodu
Örnek
Şöyle yaparız. 2 veya 6 eleman uzunluğunda bir liste yaratır. Listede isim veya şehir ismi bulunur
Faker faker = new Faker();

List<String> names =
  faker.collection(
    () -> faker.name().fullName(),
    () -> faker.address().city())
  .len(2, 6)
  .generate();

System.out.println(names);
lorem metodu
Örnek - words
Şöyle yaparız
Faker faker = new Faker();
faker.lorem().words(10)
number metodu
Örnek - numberBetween
Şöyle yaparız
Faker faker = new Faker();
for (int i = 0; i < 1000; i++) {
  String name = faker.name().fullName();
  String gender = faker.gender().binaryTypes().toUpperCase();
  int age = faker.number().numberBetween(18, 65);
  int externalId = faker.number().numberBetween(100000, 999999);
  ...  
}
stream metodu
Örnek
Şöyle yaparız. Stream içinde 20 - 22 değerleri arasında double bulunur
Faker faker = new Faker();

Stream<Object> objects =
  faker.<Object>stream(() -> faker.random().nextDouble(20, 22))
  .generate();

objects.forEach(System.out::println);
Örnek
Şöyle yaparız. Stream içinde 20 - 22 değerleri arasında double bulunur. Stream büyüklüğü 3 veya 5 arasındadır
Faker faker = new Faker();

Stream<Object> objects =
  faker.<Object>stream(() -> faker.random().nextDouble(20, 22))
  .len(3, 5)
  .generate();

objects.forEach(System.out::println);
CsvTransformer Sınıfı
Açıklaması şöyle
The CsvTransformer builder also gives you several parameters to use, such as: 

- quote(), in case you want to change the default(") quote.

- separator(), the character which delimits columns in rows.

- header() the boolean parameter, which toggles the generation of the header in the resulting CSV.
generate metodu
Örnek
Şöyle yaparız
// Datafaker supports any locale from the Locale package
Faker faker = new Faker(Locale.GERMANY); 

// Define a schema, which we can use in any Transformer
Schema<Object, ?> schema = Schema.of( 
  field("firstName", () -> faker.name().firstName()),
  field("lastName", () -> faker.name().lastName()),
  field("phoneNumber", () -> faker.phoneNumber().phoneNumberInternational()));

// Instantiate CsvTransformer using the appropriate builder.
CsvTransformer<Object> csvTransformer = CsvTransformer.builder().build(); 

// This is where the magic happens. Call the `generate` method on the 
// transformer with your schema plus the number of records to get the result as a string.
System.out.println(csvTransformer.generate(schema, 5)); 
Çıktı şöyle
"firstName";"lastName";"phoneNumber"

"Jannik";"Ripken";"+49 9924 780126"

"Frederike";"Birkemeyer";"+49 2933 945975"

"Michelle";"Steuk";"+49 7033 814683"

"Ellen";"Semisch";"+49 9537 991422"

"Oskar";"Habel";"+49 3626 169891"
JavaObjectTransformer Sınıfı
apply metodu
Örnek
Şöyle yaparız
// Java Record with 3 properties
public record Client(String firstName, String lastName, String phoneNumber) {}

// provide the Schema for our class.
Faker faker = new Faker();
JavaObjectTransformer jTransformer = new JavaObjectTransformer();

Schema<Object, ?> schema = Schema.of(
  field("firstName", () -> faker.name().firstName()),
  field("lastName", () -> faker.name().lastName()),
  field("phoneNumber", () -> faker.phoneNumber().phoneNumberInternational())
);

System.out.println(jTransformer.apply(Client.class, schema));

// Output 
// Client{firstName='Elton', lastName='Conroy', phoneNumber='+1 808-239-0480'}
BaseFaker Sınıfı
populate metodu - Class + Class Üzerinde @FakeForSchema Anotasyonu
Örnek 
Şöyle yaparız
// com.datafaker.DatafakerSchema class
public static Schema<Object, ?> defaultSchema() {
  var faker = new Faker(Locale.UK, new RandomService(new Random(1)));
  return Schema.of(
    field("firstName", () -> faker.name().firstName()),
    field("lastName", () -> faker.name().lastName()),
    field("phoneNumber", () -> faker.phoneNumber().phoneNumberInternational())
  );
}

@FakeForSchema("com.datafaker.DatafakerSchema#defaultSchema")
public record Client(String firstName, String lastName, String phoneNumber) { }

// Then you can use net.datafaker.providers.base.BaseFaker.populate(java.lang.Class<T>) 
// to populate the object with the default predefined schema.
Client client = BaseFaker.populate(Client.class);
populate metodu - Class + Schema
Örnek 
Şöyle yaparız
// Alternatively, you can use 
// net.datafaker.providers.base.BaseFaker.populate(
//  java.lang.Class<T>, 
//   net.datafaker.schema.Schema<java.lang.Object, ?>) 
//  to populate the object with a custom schema:
Client client = BaseFaker.populate(Client.class, Schema.of(
  field("firstName", () -> faker.name().firstName()),
  field("lastName", () -> faker.name().lastName()),
  field("phoneNumber", () -> faker.phoneNumber().phoneNumberInternational())
));