java

Spring Boor Performance

Logging

  • hibernate.show_sql (niet in prod!)
  • hibernate.format_sql
  • hibernate.use_sql_comments

params: - org.hibernate.type.descriptor.sql level trace

P6Spy

pom.xml

<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version> <!-- You can use the latest version -->
</dependency>

application.properties

spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/your_database
spring.datasource.username=your_username
spring.datasource.password=your_password

databaseDialectDateFormat=yyyy-MM-dd'T'HH:mm:ss.SSSZ
customLogMessageFormat=%{currentTime}|%(executionTime)ms|%(category)|connection %(connectionId)|\n%(sqlSingleLine)

modulelist=com.p6spy.engine.outage.P6OutageFactory,com.p6spy.engine.logging.P6LogFactory
logfile=spy.log
logMessageFormat=com.p6spy.engine.spy.appender.SingleLineFormat
appender=com.p6spy.engine.spy.appender.Slf4JLogger
databaseDialectDateFormat=yyyy-MM-dd HH:mm:ss.SSS
•   modulelist: Specifies which P6Spy modules to enable.
•   logfile: Defines where the log file will be created.
•   appender: Determines where logs should be sent, such as a file or SLF4J for logging through a standard logging framework.

Datasource-proxy

@Bean
public DataSource dataSource() {
    SLF4JQueryLoggingListener loggingListener = new SLF4JQueryLoggingListener();
    loggingListener.setQueryLogEntryCreator(new InlineQueryLogEntryCreator());

    return ProxyDataSourceBuilder.create(actualDataSource())
        .name(DATA_SOURCE_PROXY_NAME)
        .listener(loggingListener)
        .build();
}
  • can have own cutom statement execution listeners

FetchSize

statement.setFetchSize(fetchSize) 10 Oracle 120 SQL PostreSQL MySQL whole resultset in single roundtrip

JPA2.2 getResultStream dan wel zetten voor PostgresQL en MySQL

Streams

MySQL streaming

One Record

statement.setFetchSize(Integer.MIN_VALUE)

Multiple Records

statement.setFetchSize(fetchSize)

Oracle

spring.jpa.properties.hibernate.jdbc.fetch_size=50

MySQL Postgres

@Query("""
    select p
    from Post p
    where date(p.createdOn) >= :sinceDate
""")
@QueryHints(
    @QueryHint(name = AvailableHints.HINT_FETCH_SIZE, value = "25")
)
Stream<Post> streamByCreatedOnSince(
    @Param("sinceDate") LocalDate sinceDate
);

Pagination

Data grows per page

  • FETCH FIRST N ROWS ONLY
  • FETCH NEXT N ROWS ONLY
  • OFFSET M ROWS Oracle 12c SQL2012 PostgresQL 8.4

let op ORDER BY

Top-N

SELECT title
FROM post
ORDER BY created_on DESC, id DESC
FETCH FIRST 5 ROWS ONLY

Next-N

SELECT title
FROM post
ORDER BY created_on DESC, id DESC
OFFSET 5 ROWS
FETCH NEXT 5 ROWS ONLY

PostgreSQL MySQL TOP-N

SELECT title
FROM post
ORDER BY created_on DESC, id DESC
LIMIT 5

PostgreSQL MySQL NEXT-N

SELECT title
FROM post
ORDER BY created_on DESC, id DESC
LIMIT 5
OFFSET 5

NB: OFFSET komt NA LIMIT!!!

JPQL Querying -Pagination

Page<Post> findAllByTitle(
    @Param("titlePattern") String titlePattern,
    Pageable pageRequest
);

@Query("""
    select p
    from Post p
    where p.title like :titlePattern
""")
Page<Post> findAllByTitle(
    @Param("titlePattern") String titlePattern,
    Pageable pageRequest
);

JPQL QUERY Pagination Top-N

Page<Post> posts = postRepository.findAllByTitle(
    "High-Performance Java Persistence %",
    PageRequest.of(0, 25, Sort.by("createdOn"))
);
SELECT p.id, p.created_on, p.title
FROM post p
WHERE p.title LIKE 'High-Performance Java Persistence %' ESCAPE ''
ORDER BY p.created_on ASC
OFFSET 0 ROWS
FETCH FIRST 25 ROWS ONLY
@Query(value = """
    SELECT p.id, p.title, p.created_on
    FROM post p
    WHERE p.title ilike :titlePattern
    ORDER BY p.created_on
""",
nativeQuery = true)
Page<Post> findAllByTitleLike(
    @Param("titlePattern") String titlePattern,
    Pageable pageRequest
);

Top-N

Page<Post> posts = postRepository.findAllByTitle(
    "High-Performance Java Persistence %",
    PageRequest.of(0, 25)
);
SELECT p.id, p.title, p.created_on
FROM post p
WHERE p.title ilike 'High-Performance Java Persistence %'
ORDER BY p.created_on
FETCH FIRST 25 ROWS ONLY

Offset pagination index scanning performance

CREATE INDEX idx_post_created_on ON post (created_on DESC, id DESC);

SELECT id
FROM post
ORDER BY created_on DESC
LIMIT 50;
Limit  (cost=0.28..2.51 rows=50 width=16)
  (actual time=0.013..0.022 rows=50 loops=1)
  -> Index Scan using idx_post_created_on on post p
     (cost=0.28..223.28 rows=5000 width=16)
     (actual time=0.013..0.019 rows=50 loops=1)
Planning time: 0.113 ms
Execution time: 0.055 ms

2e en latere scan

SELECT id
FROM post
ORDER BY created_on DESC
LIMIT 50
OFFSET 50;
Limit  (cost=2.51..4.74 rows=50 width=16)
  (actual time=0.032..0.044 rows=50 loops=1)
  -> Index Scan using idx_post_created_on on post p
     (cost=0.28..223.28 rows=5000 width=16)
     (actual time=0.022..0.040 rows=100 loops=1)
Planning time: 0.198 ms
Execution time: 0.071 ms

Nu 100rows!!! Op de laattste pagina wordt alles gescanned... 1.190ms..... OFFSET doesnt seek/traverse

Seek or keyset pagination

SELECT id, created_on
FROM post
ORDER BY created_on DESC, id DESC
LIMIT 50

created_on EN id moeten ERIN

Next-N

SELECT id, created_on
FROM post
WHERE (created_on, id) < ('2024-10-02 21:00:00.0', 4951)
ORDER BY created_on DESC, id DESC
LIMIT 50;
•   The row value expression (a, b) < (c, d) is PostgreSQL and MySQL.
•   It’s equivalent to a < c | (a = c & b < d).

Nu wel max 50 results in set

Spring Data JPA - WindowIterator

Oplossing!

WindowIterator<PostComment> commentWindowIterator = WindowIterator.of(
    position -> postCommentRepository.findByPost(
        post,
        PageRequest.of(
            0, pageSize,
            Sort.by(
                Sort.Order.desc(PostComment_.CREATED_ON),
                Sort.Order.desc(PostComment_.ID)
            )
        ),
        position
    )
).startingAt(ScrollPosition.keyset());

commentWindowIterator.forEachRemaining(this::processPostComment);

met Blaze Persistance Top-N

// Blaze Persistence – Keyset pagination
PagedList<Post> firstPage = cbf
    .create(entityManager, Post.class)
    .orderByAsc(Post_.CREATED_ON).orderByAsc(Post_.ID)
    .page(0, pageSize)
    .withKeysetExtraction(true)
    .getResultList();
SELECT p.id, p.created_on, p.title,
       (SELECT count(*) FROM post)
FROM post p
ORDER BY p.created_on, p.id
OFFSET 0
ROWS FETCH FIRST 25 ROWS ONLY;

Next N

// Blaze Persistence – Keyset pagination
PagedList<Post> nextPage = cbf
    .create(entityManager, Post.class)
    .orderByAsc(Post_.CREATED_ON).orderByAsc(Post_.ID)
    .page(postPage.getKeysetPage(),
          postPage.getPage() * postPage.getMaxResults(),
          postPage.getMaxResults())
    .getResultList();
SELECT p.id, p.created_on, p.title,
       (SELECT count(*) FROM post)
FROM post p
WHERE ('2024-09-09 12:10:00.0', 10) < (p.created_on, p.id)
ORDER BY p.created_on, p.id
OFFSET 0
ROWS FETCH FIRST 25 ROWS ONLY;

Projections

Fetching too many columns

Instead of fetching all columns:

SELECT *  
FROM post_comment pc  
LEFT JOIN post p ON p.id = pc.post_id  
LEFT JOIN post_details pd ON p.id = pd.id  


Fetch a custom SQL projection:


SELECT pc.id, pc.review FROM post_comment pc LEFT JOIN post p ON p.id = pc.post_id LEFT JOIN post_details pd ON p.id = pd.id

(Joins zijn niet nodig... => dus tweede is dan sneller)

Tuple projection

not type save

• The JPA Tuple wraps the default Object[] projection and allows you to retrieve the column values via their aliases.

List<Tuple> commentTuples = postRepository.findAllCommentTuplesByPostTitle(titleToken);
Tuple commentTuple = commentTuples.get(0);

long id = commentTuple.get("id", Number.class).longValue();
String title = commentTuple.get("title", String.class);

Interface-based projection

• If you want a type-safe projection, Spring Data JPA provides the option of wrapping the result in a Proxy based on a given interface.

The Interface-based projection can be used like this:

@Query("""
select
p.id as id,
p.title as title,
c.review as review
from PostComment c
join c.post p
where p.title like :postTitle
order by c.id
""")
List<PostCommentSummary> findAllCommentSummariesByPostTitle(
@Param("postTitle") String postTitle
);

// nu type safe!       
Long id = commentSummary.getId();
String title = commentSummary.getTitle();

Met ingebakken (!) DTOs en Records

DTOs

properties.put(
    "hibernate.integrator_provider",  // Hibernate property for custom integrator
    (IntegratorProvider) () -> Collections.singletonList( // Lambda to provide integrator
        new ClassImportIntegrator( // Hypersistence Utils integrator
            List.of( // List of classes to register
                PostCommentDTO.class,  // First DTO class
                PostCommentRecord.class // Second DTO class
            )
        )
    )
);

String jpql = "SELECT new PostCommentDTO(pc.id, pc.comment) FROM PostComment pc WHERE pc.post.id = :postId";


Records

public record PostCommentDTO(Long id, String comment) {}
public record PostCommentRecord(Long id, String content) {}

properties.put(
    "hibernate.integrator_provider",
    (IntegratorProvider) () -> Collections.singletonList(
        new ClassImportIntegrator(
            List.of(
                PostCommentDTO.class,   // Register the record as a DTO
                PostCommentRecord.class // Register additional records as needed
            )
        )
    )
);

@Query(
    value = "SELECT pc.id, pc.comment FROM post_comment pc WHERE pc.post_id = :postId",
    nativeQuery = true
)
List<PostCommentDTO> findCommentsByPostId(@Param("postId") Long postId);

Long id = commentRecord.id();
String title = commentRecord.title();

TupleTransformer and ResultListTransformer

The SQL projection can contain data from multiple tables, like the post and post_comment, which form a one-to-many table relationship.

SELECT p.id AS p_id,
p.title AS p_title,
pc.id AS pc_id,
pc.review AS pc_review
FROM post p
JOIN post_comment pc ON p.id = pc.post_id
ORDER BY pc.id

komt uit:

p_id p_title pc_id pc_review
1 High-Performance Java Persistence 1 Best book on JPA and Hibernate!
1 High-Performance Java Persistence 2 A must-read for every Java developer!
2 Hypersistence Optimizer 3 It's like pair programming with Vlad!

Spring snips

Docker

local

docker run -v ./app.jar:/app/app.jar \
-e DB_USERNAME='admin' -e DB_PASSWORD='password123' \
-p 8080:8080 \
amazoncorretto:21 \
java -jar /app/app.jar --spring.profiles.active=prod

start met .properties file

DB_USERNAME=user java -jar target/app.jar --spring.profiles.active=prod

Application properties

in application.prod.properties

spring.application.version=@project.version@

// h2
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.url=jdbc:h2:mem:contactdb
spring.h2.console.enabled=true

// MySQL
spring.datasource.url=jdbc:mysql://localhost:3306/tempdb?useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=UTC&createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=<YOURPASS>
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Hibernate JPA settings
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

# Logging level for debugging
logging.level.org.hibernate=DEBUG

logging.level.root=debug

actuator

voor in dev:

management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.server.port=9090

Intellij Endpoint POST Request

POST http://localhost:8080/players
Accept: */*
Accept-Encoding: gzip, deflate
Content-Type: application/json
Accept-Language: en-us

{
 "role" : "value3",
 "name": "value4"
}

Lombok

Log4j2 Pattern Layout Alternative

configure Log4j2 to automatically include the class and method names in log messages by setting up a custom pattern in log4j2.xml:

<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p [%t] %C{1}.%M - %msg%n"/>

Swagger

`` org.springdoc springdoc-openapi-starter-webmvc-ui 2.6.0 ```

http://localhost:8080/swagger-ui/index.html

en json format
http://localhost:8080/v3/api-docs

Profiles

in yaml -- maakt er feitelijk 2 yaml docs van

in .properties:

spring.profiles.active=sql
spring.config.activate.on-profile=sql

Kafka

https://kafka.apache.org/quickstart

// download
10015  tar -xzf kafka_2.13-3.8.0.tgz
// unpack and start
10016  cd kafka_2.13-3.8.0
10019  cd ~/Downloads/kafka_2.13-3.8.0
10020  bin/kafka-server-start.sh config/server.properties
10022  bin/kafka-topics.sh --create --topic quickstart-events --bootstrap-server localhost:9092
10023  bin/kafka-topics.sh --describe --topic quickstart-events --bootstrap-server localhost:9092
10024  bin/kafka-console-producer.sh --topic quickstart-events --bootstrap-server localhost:9092
10026  bin/kafka-console-consumer.sh --topic quickstart-events --from-beginning --bootstrap-server localhost:9092
10027  bin/kafka-server-start.sh config/server.properties
// consume
10029  bin/kafka-console-producer.sh --topic quickstart-events --bootstrap-server localhost:9092
10030  bin/kafka-console-consumer.sh --topic quickstart-events --from-beginning --bootstrap-server localhost:9092
10032  bin/kafka-topics.sh --describe --topic cab-location --bootstrap-server localhost:9092
10033  bin/kafka-console-consumer.sh --topic cab-location --from-beginning --bootstrap-server localhost:9092

application properties Driver

spring.application.name=kafkaBookingDriver
spring.kafka.producer.bootstrap-servers=localhost:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
server.port=8082

application properties User

spring.application.name=kafkaBookingUser

spring.kafka.consumer.bootstrap-servers=localhost:9092
spring.kafka.consumer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.consumer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.consumer.auto-offset-reset=earliest
server.port=8081

spring.kafka.consumer.group-id=user-group

Driver

needs: - config

  • Controller

  • service

config:

package nl.appall.java.spring.kafkabookingdriver.config;

import org.apache.kafka.clients.admin.NewTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.TopicBuilder;

import static nl.appall.java.spring.kafkabookingdriver.constant.AppConstant.CAB_LOCATION;

@Configuration
public class KafkaConfig {


    @Bean
    public NewTopic topic() {
        return TopicBuilder
                .name(CAB_LOCATION)
                .build();
    }
}

controller:

package nl.appall.java.spring.kafkabookingdriver.controller;

import nl.appall.java.spring.kafkabookingdriver.service.CabLocationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/location")
public class CabLocationController {

    @Autowired private CabLocationService cabLocationService;

    @PutMapping
    public ResponseEntity updateLaction() throws InterruptedException {

        int range = 100;
        while(range > 0) {
            cabLocationService.updateLocation(Math.random() + " , "+ Math.random());
            Thread.sleep(1000);
            range--;
        }
        return new ResponseEntity<>(Map.of("message","Location Updated"), HttpStatus.OK);
    }
}

service:

package nl.appall.java.spring.kafkabookingdriver.service;

import nl.appall.java.spring.kafkabookingdriver.constant.AppConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class CabLocationService {

    @Autowired private KafkaTemplate<String,Object> kafkaTemplate;

    public boolean updateLocation(String location){
        kafkaTemplate.send(AppConstant.CAB_LOCATION, location);
        return true;
    }
}

User

needs only service

package nl.appall.java.spring.kafkabookinguser;

import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;

@Service
public class LocationService {

    @KafkaListener(
            topics = "cab-location",
            groupId = "user-group"
    )
    public void cabLocation(String location){
        System.out.println(location);

    }
}

Tags: 

OCP Chapter 22

Security

LIMITING ACCESSIBILITY

package animals.security;
public class ComboLocks {
   public Map<String, String> combos;
}

combos object has public access. This is also poor encapsulation. A key security principle is to limit access as much as possible. Think of it as “need to know” for objects. This is called the principle of least privilege.

better:

package animals.security;
public class ComboLocks {
private Map<String, String> combos;
   public boolean isComboValid(String animal, String combo) {
      var correctCombo = combos.get(animal);
      return combo.equals(correctCombo);
} }

RESTRICTING EXTENSIBILITY

public class GrasshopperCage {
   public static void openLock(ComboLocks comboLocks, String
combo) {
      if (comboLocks.isComboValid("grasshopper", combo))
            System.out.println("Open!");
} }

// nu is mogelijk
public class EvilComboLocks extends ComboLocks {
   public boolean isComboValid(String animal, String combo) {
      var valid = super.isComboValid(animal, combo);
      if (valid) {
         // email the password to Hacker Harry
      }
      return valid;
   }
}

// nu is niet meer mogelijk

public **final** class ComboLocks { private Map<String, String> combos;
   // instantiate combos object
   public boolean isComboValid(String animal, String combo) {
      var correctCombo = combos.get(animal);
      return combo.equals(correctCombo);
} }

CREATING IMMUTABLE OBJECTS

Although there are a variety of techniques for writing an immutable class, you should be familiar with a common strategy for making a class immutable. 1. Mark the class as final. 2. Mark all the instance variables private. 3. Don't define any setter methods and make fields final. 4. Don't allow referenced mutable objects to be modified. 5. Use a constructor to set all properties of theo bject,making a copy if needed.

  import java.util.*;

  public final class Animal {

        private final ArrayList<String> favoriteFoods;

        public Animal() {
           this.favoriteFoods = new ArrayList<String>();
           this.favoriteFoods.add("Apples");

     }

    public List<String> getFavoriteFoods() {
        return favoriteFoods;
    }
}
// kwetsbaar omdat favoriteFoods gewijzigd kan worden bv:  getFavoriteFoods().clear()

// dit zou al helpen ipv vorige getter
public int getFavoriteFoodsCount() {
    return favoriteFoods.size();
}
public String getFavoriteFoodsElement(int index) {
   return favoriteFoods.get(index);
}

// of
public ArrayList<String> getFavoriteFoods() {
   return new ArrayList<String>(this.favoriteFoods);
}

Let's say we want to allow the user to provide the favoriteFoods data, so we implement the following:


public Animal(ArrayList<String> favoriteFoods) { if(favoriteFoods == null) throw new RuntimeException("favoriteFoods is this.favoriteFoods = favoriteFoods; } public int getFavoriteFoodsCount() { return favoriteFoods.size(); } public String getFavoriteFoodsElement(int index) { return favoriteFoods.get(index); } kan dan en dan is favfoods niet meer immutable: void modifyNotSoImmutableObject() { var favorites = new ArrayList<String>(); favorites.add("Apples"); var animal = new Animal(favorites); System.out.print(animal.getFavoriteFoodsCount()); favorites.clear(); System.out.print(animal.getFavoriteFoodsCount()); } // The solution is to use a copy constructor to make a copy of the list object containing the same elements. public Animal(List<String> favoriteFoods) { if(favoriteFoods == null) throw new RuntimeException("favoriteFoods is required"); this.favoriteFoods = new ArrayList<String> (favoriteFoods); }

CLONING OBJECTS

ava has a Cloneable interface that you can implement if you want classes to be able to call the clone() method on your object. This helps with making defensive copies.

this.favoriteFoods = (ArrayList) favoriteFoods.clone();

// dan Clonable gebruyiken:
public final class Animal implements Cloneable {

//en
public static void main(String... args) throws Exception {
   ArrayList<String> food = new ArrayList<>();
   food.add("grass");
   Animal sheep = new Animal(food);
   Animal clone = (Animal) sheep.clone();
   System.out.println(sheep == clone);
   System.out.println(sheep.favoriteFoods == clone.favoriteFoods);
}

By default, the clone() method makes a shallow copy of the data, which means only the top‐level object references and primitives are copied. No new objects from within the cloned object are created.

By contrast, you can write an implementation that does a deep copy and clones the objects inside. A deep copy does make a new ArrayList object. Changes to the cloned object do not affect the original.


public Animal clone() { ArrayList<String> listClone = (ArrayList) favoriteFoods.clone(); return new Animal(listClone); }

myObject.clone() | V Implements Clonable? -> No -> Throws Exception | V Overrides Clone() -> No -> Shallow copy | V Implementation dependent

In the last block, implementation‐dependent means you should probably check the Javadoc of the overridden clone() method before using it.

Shallow Copy

A shallow copy of an object is a new instance of that object where the fields of the original object are copied as they are. However, if the object contains references to other objects (i.e., non-primitive fields), only the references are copied, not the actual objects they refer to.

        Address address = new Address("New York");
        Person person1 = new Person("John", address);
        Person person2 = (Person) person1.clone();

        System.out.println(person1.address.city); // Outputs: New York
        System.out.println(person2.address.city); // Outputs: New York

        person2.address.city = "Los Angeles";

        System.out.println(person1.address.city); // Outputs: Los Angeles
        System.out.println(person2.address.city); // Outputs: Los Angeles

In this example, person1 and person2 are separate objects, but they share the same Address object. If you change the city in person2, it will also change in person1, demonstrating that only a shallow copy was made.

Deep Copy

A deep copy of an object involves creating a new object and also recursively copying all objects referenced by the original object. This means that the copy and the original object do not share references to any mutable objects. Any changes made to the deep-copied object will not affect the original object.

// nu in person.class:
@Override
    protected Object clone() throws CloneNotSupportedException {
        Person clonedPerson = (Person) super.clone();
        clonedPerson.address = new Address(this.address); // Deep copy of Address
        return clonedPerson;
    }

// en dan
Address address = new Address("New York");
        Person person1 = new Person("John", address);
        Person person2 = (Person) person1.clone();

        System.out.println(person1.address.city); // Outputs: New York
        System.out.println(person2.address.city); // Outputs: New York

        person2.address.city = "Los Angeles";

        System.out.println(person1.address.city); // Outputs: New York
        System.out.println(person2.address.city); // Outputs: Los Angeles

Shallow copying is faster and uses less memory, but deep copying ensures that the two objects are entirely independent of each other.

Introducing Injection and Input Validation

Injection is an attack where dangerous input runs in a program as part of a command. Kan bv met Statement met raw SQL.

"monday' OR day IS NOT NULL OR day = 'sunday"

An exploit is an attack that takes advantage of weak security.

There are many sources of untrusted data. For the exam, you need to be aware of user input, reading from files, and retrieving data from a database. In the real world, any data that did not originate from your program should be considered suspect.

Using PreparedStatement

If you remember only two things about SQL and security, remember to use a PreparedStatement and bind variables.

INVALIDATING INVALID INPUT WITH VALIDATION

SQL injection isn't the only type of injection. Command injection is another type that uses operating system commands to do something unexpected.

Console console = System.console();
String dirName = console.readLine();
Path path = Paths.get("c:/data/diets/" + dirName);
try (Stream<Path> stream = Files.walk(path)) {
   stream.filter(p -> p.toString().endsWith(".txt"))
      .forEach(System.out::println);
}
// als je input .. is dan zie je de secrets dir... dus validate input

// bv whitelist
`if (dirName.equals("mammal") || dirName.equals("birds")) {
`


A blacklist is a list of things that aren't allowed. In the previous example, we could have put the dot ( .) character on a blacklist. The problem with a blacklist is that you have to be cleverer than the bad guys. There are a lot of ways to cause harm. For example, you can encode characters.

By contrast, the whitelist is specifying what is allowed. You can supply a list of valid characters. Whitelisting is preferable to blacklisting for security because a whitelist doesn't need to foresee every possible problem.

Working with Confidential Information

GUARDING SENSITIVE DATA FROM OUTPUT

The first step is to avoid putting confidential information in a toString() method. Dus oppassen bij - Writing to a log file - Printing an exception or stack trace - System.out and System.err messages - Writing to data files

PROTECTING DATA IN MEMORY

if crashes:

When calling the readPassword() on Console, it returns a char[] instead of a String. This is safer for two reasons. - It is not stored as a String, so Java won't place it in the String pool, where it could exist in memory long after the code that used it is run. - You can null out the value of the array element rather than waiting for the garbage collector to do it.

Console console = System.console();
char[] password = console.readPassword();
Arrays.fill(password, 'x');

When the sensitive data cannot be overwritten, it is good practice to set confidential data to null when you're done using it. If the data can be garbage collected, you don't have to worry about it being exposed later.

LocalDate dateOfBirth = getDateOfBirth();
// use date of birth
dateOfBirth = null;

The idea is to have confidential data in memory for as short a time as possible.

LIMITING FILE ACCESS

Another way is to use a security policy to control what the program can access.

It is good to apply multiple techniques to protect your application. This approach is called defense in depth.

// permissie read only
grant {
   permission java.io.FilePermission
    "C:\\water\\fish.txt",
    "read";
};

// permissie read write
grant {
   permission java.io.FilePermission
    "C:\\water\\fish.txt",
    "read, write";
};


When looking at a policy, pay attention to whether the policy grants access to more than is needed to run the program. If our application needs to read a file, it should only have read permissions. This is the principle of least privilege we showed you earlier.

Serializing and Deserializing Objects

Imagine we are storing data in an Employee record. We want to write this data to a file and read this data back into memory, but we want to do so without writing any potentially sensitive data to disk. From Chapter 19, you should already know how to do this with serialization.

import java.io.*;
public class Employee implements Serializable {
   private String name;
   private int age;
   // Constructors/getters/setters
}

SPECIFYING WHICH FIELDS TO SERIALIZE

marking a field as transient prevents it from being serialized.

private transient int age;

// Alternatively, you can specify fields to be serialized in an array.

private static final ObjectStreamField[]
serialPersistentFields =
   { new ObjectStreamField("name", String.class) };

You can think of serialPersistentFields as the opposite of transient. The former is a whitelist of fields that should be serialized, while the latter is a blacklist of fields that should not.

If you go with the array approach, make sure you remember to use the private, static, and final modifiers. Otherwise, the field will be ignored.

CUSTOMIZING THE SERIALIZATION PROCESS

Security may demand custom serialization.

Take a look at the following implementation that uses writeObject() and readObject() for serialization

import java.io.*;
public class Employee implements Serializable {
   private String name;
   private String ssn;
   private int age;

   // Constructors/getters/setters

   private static final ObjectStreamField[] serialPersistentFields =
            { new ObjectStreamField("name", String.class), new ObjectStreamField("ssn", String.class) };

    private static String encrypt(String input) {
      // Implementation omitted
    }
    private static String decrypt(String input) {
      // Implementation omitted
    }

    private void writeObject(ObjectOutputStream s) throws Exception {
          ObjectOutputStream.PutField fields = s.putFields();
          fields.put("name", name);
          fields.put("ssn", encrypt(ssn));
          s.writeFields();
    }

    private void readObject(ObjectInputStream s) throws Exception {
            ObjectInputStream.GetField fields = s.readFields();

            this.name = (String)fields.get("name", null);
            this.ssn = decrypt((String)fields.get("ssn", null));
    }

}

This version skips the age variable as before, although this time without using the transient modifier. It also uses custom read and write methods to securely encrypt/decrypt the Social Security number. Notice the PutField and GetField classes are used in order to write and read the fields easily.

PRE/POST‐SERIALIZATION PROCESSING

import java.io.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Employee implements Serializable {
   ...
   private Employee() {}
   private static Map<String,Employee> pool =
      new ConcurrentHashMap<>();
   public synchronized static Employee getEmployee(String
name) {
      if(pool.get(name)==null) {
         var e = new Employee();
         e.name = name;         pool.put(name, e);
}
      return pool.get(name);
   }
}

This method creates a new Employee if one does not exist. Otherwise, it returns the one stored in the memory pool.

Applying readResolve()

Now we want to start reading/writing the employee data to disk, but we have a problem. When someone reads the data from the disk, it deserializes it into a new object, not the one in memory pool. This could result in two users holding different versions of the Employee in memory!

Enter the readResolve() method. When this method is present, it is run after the readObject() method and is capable of replacing the reference of the object returned by deserialization.

import java.io.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Employee implements Serializable {
   ...
    public synchronized Object readResolve() throws ObjectStreamException {
      var existingEmployee = pool.get(name);
      if(pool.get(name) == null) {
        // New employee not in memory
        pool.put(name, this);
        return this;
     } else {
        // Existing user already in memory
        existingEmployee.name = this.name;
        existingEmployee.ssn = this.ssn;
        return existingEmployee;
    }
 }
}

If the object is not in memory, it is added to the pool and returned. Otherwise, the version in memory is updated, and its reference is returned. Notice that we added the synchronized modifier to this method. Java allows any method modifiers (except static) for the readResolve() method including any access modifier. This rule applies to writeReplace(), which is up next.

Applying writeReplace()

Now, what if we want to write an Employee record to disk but we don't completely trust the instance we are holding? For example, we want to always write the version of the object in the pool rather than the this instance. By construction, there should be only one version of this object in memory, but for this example let's pretend we're not 100 percent confident of that.

The writeReplace() method is run before writeObject() and allows us to replace the object that gets serialized.

import java.io.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Employee implements Serializable {
   ...
    public Object writeReplace() throws ObjectStreamException {
      var e = pool.get(name);
      return e != null ? e : this;
    }
}

This implementation checks whether the object is found in the pool. If it is found in the pool, that version is sent for serialization; otherwise, the current instance is used.

Return type Method Parameters Description
Object writeReplace None Allows replacement of object before serialization
void writeObject() ObjectInputStream Serializes optionally using PutField
void readObject() ObjectOutputStream Deserializes optionally using GetField
Object readResolve() None Allows replacement of object after deserialization

Making Methods final

Making Classes final

Making Constructor private

public class FoodOrder {
   private String item;
   private int count;

    private FoodOrder(String item, int count) { setItem(item);
        setCount(count);
    }
-> public FoodOrder getOrder(String item, int count) {
      return new FoodOrder(item, count);
   }
   public String getItem() { return item; }
   public void setItem(String item) { this.item = item; }
   public int getCount() { return count; }
   public void setCount(int count) { this.count = count; }
}

HOW TO PROTECT THE SOURCE CODE

jars.....

Preventing Denial of Service Attacks

A denial of service (DoS) attack is when a hacker makes one or more requests with the intent of disrupting legitimate requests. Most denial of service attacks require multiple requests to bring down their targets. Some attacks send a very large request that can even bring down the application in one shot.

By contrast, a distributed denial of service (DDoS) attack is a denial of service attack that comes from many sources at once. For example, many machines may attack the target.

LEAKING RESOURCES

READING VERY LARGE RESOURCES

Another source of a denial of service attacks is very large resources.

public void transform(Path in, Path out) throws IOException  {
   var list = Files.readAllLines(in);
   list.removeIf(s -> s.trim().isBlank());
   Files.write(out, list);
}

gaat mis op grote files want je geheugen loopt vol... To prevent this problem, you can check the size of the file before reading it.

INCLUDING POTENTIALLY LARGE RESOURCES

An inclusion attack is when multiple files or components are embedded within a single file. Any file that you didn't create is suspect. Some types can appear smaller than they really are. For example, some types of images can have a “zip bomb” where the file is heavily compressed on disk. When you try to read it in, the file uses much more space than you thought.

Extensible Markup Language (XML) files can have the same problem. One attack is called the “billion laughs attack” where the file gets expanded exponentially.

OVERFLOWING NUMBERS

When checking file size, be careful with an int type and loops. Since an int has a maximum size, exceeding that size results in integer overflow. Incrementing an int at the maximum value results in a negative number, so validation might not work as expected.

public static void main(String[] args) {
   System.out.println(enoughRoomToAddLine(100));
   System.out.println(enoughRoomToAddLine(2_000_000));
   System.out.println(enoughRoomToAddLine(Integer.MAX_VALUE));
}
public static boolean enoughRoomToAddLine(int requestedSize) {
   int maxLength = 1_000_000;
   String newLine = "END OF FILE";
   int newLineSize = newLine.length();
   return requestedSize + newLineSize < maxLength;
}

//true
//false
//true

WASTING DATA STRUCTURES

One advantage of using a HashMap is that you can look up an element quickly by key. Even if the map is extremely large, a lookup is fast as long as there is a good distribution of hashed keys.

  • Identify ways of preventing a denial of service attack. Using a try‐with‐ resources statement for all I/O and JDBC operations prevents resource leaks. Checking the file size when reading a file prevents it from using an unexpected amount of memory. Confirming large data structures are being used effectively can prevent a performance problem.
  • Protect confidential information in memory. Picking a data structure that minimizes exposure is important. The most common one is using char[] for passwords. Additionally, allowing confidential information to be garbage collected as soon as possible reduces the window of exposure.
  • Compare injection, inclusion, and input validation. SQL injection and command injection allow an attacker to run expected commands. Inclusion is when one file includes another. Input validation checks for valid or invalid characters from users.
  • Design secure objects. Secure objects limit the accessibility of instance variables and methods. They are deliberate about when subclasses are allowed. Often secure objects are immutable and validate any input parameters.
  • Write serialization and deserializaton code securely. The transient modifier signifies that an instance variable should not be serialized. Alternatively, serialPersistenceFields specifies what should be. The readObject(), writeObject(), readResolve(), and writeReplace() methods are optional methods that provide further control of the process.

prev next

Tags: 

OCP Chapter 21

JDBC

Java Database Connectivity Language (JDBC): Accesses data as rows and columns.

Java Persistence API (JPA): Accesses data through Java objects using a concept called object‐relational mapping (ORM).

A relational database is accessed through Structured Query Language (SQL). SQL is a programming language used to interact with database records. JDBC works by sending a SQL command to the database and then processing the response.
SQL keywords are case insensitive.

In addition to relational databases, there is another type of database called a NoSQL database. This is for databases that store their data in a format other than tables, such as key/value, document stores, and graph‐based databases. NoSQL is out of scope for the exam as well.

interfaces in the JDK: - Driver: Establishes a connection to the database - Connection: Sends commands to a database - PreparedStatement: Executes a SQL query - CallableStatement: Executes commands stored in the database - ResultSet: Reads results of a query

public class MyFirstDatabaseConnection {
   public static void main(String[] args) throws SQLException
   {
    String url = "jdbc:derby:zoo";
    try (Connection conn = DriverManager.getConnection(url);
        PreparedStatement ps = conn.prepareStatement( "SELECT name FROM animal");
        ResultSet rs = ps.executeQuery()) {
        while (rs.next())
        System.out.println(rs.getString(1));
    }
   }
}

GETTING A DATABASE CONNECTION

There are two main ways to get a Connection: DriverManager or DataSource. DriverManager is the one covered on the exam. Do not use a DriverManager in code someone is paying you to write. A DataSource has more features than DriverManager. For example, it can pool connections or store the database connection information outside the application.

Connection conn = DriverManager.getConnection("jdbc:derby:zoo");
System.out.println(conn);

It does not actually execute the query yet!

JDBC URL

Unlike web URLs, a JDBC URL has a variety of formats. They have three parts in common, as shown in Figure 21.3. The first piece is always the same. It is the protocol jdbc. The second part is the subprotocol, which is the name of the database such as derby, mysql, or postgres. The third part is the subname, which is a database‐specific format. Colons ( :) separate the three parts.

Notice the three parts. It starts with jdbc and then comes the subprotocol derby, and it ends with the subname, which is the database name. The location is not required, because Derby is an in‐memory database.

jdbc:derby:zoo // The location is not required, because Derby is an in‐memory database.


jdbc:postgresql://localhost/zoo
jdbc:oracle:thin:@123.123.123.123:1521:zoo
jdbc:mysql://localhost:3306
jdbc:mysql://localhost:3306/zoo?profileSQL=true

Statement, CallableStatement, PreparedStatement

            ┌───────────────────┐
            │     Statement     │
            └─────────▲─────────┘
          ┌───────────┴────────────┐
          │                        │
          │                        │
┌───────────────────┐    ┌───────────────────┐
│ PreparedStatement │    │ CallableStatement │
└───────────────────┘    └───────────────────┘


While it is possible to run SQL directly with Statement, you shouldn't. PreparedStatement is far superior for the following reasons: - Performance: In most programs, you run similar queries multiple times. A PreparedStatement figures out a plan to run the SQL well and remembers it. - Security: As you will see in Chapter 22, “Security,” you are protected against an attack called SQL injection when using a PreparedStatement correctly - Readability: It's nice not to have to deal with string concatenation in building a query string with lots of parameters. - Future use: Even if your query is being run only once or doesn't have any parameters, you should still use a PreparedStatement. That way future editors of the code won't add a variable and have to remember to change to PreparedStatement then.

Using the Statement interface is also no longer in scope for the JDBC exam, so we do not cover it in this book.

Prepared Statement

Passing a SQL statement when creating the object is mandatory.

try (var ps = conn.prepareStatement()) { // DOES NOT COMPILE }

correct:

try (
    PreparedStatement ps = conn.prepareStatement( "SELECT * FROM exhibits")) {
        // work with ps
}

Modifying Data with executeUpdate()

10: var insertSql = "INSERT INTO exhibits VALUES(10, 'Deer',
3)";
11: var updateSql = "UPDATE exhibits SET name = '' " +
12:    "WHERE name = 'None'";
13: var deleteSql = "DELETE FROM exhibits WHERE id = 10"; 14:
15: try (var ps = conn.prepareStatement(insertSql)) {
16: int result = ps.executeUpdate();
17:    System.out.println(result); // 1
18: }
19:
20: try (var ps = conn.prepareStatement(updateSql)) {
21: int result = ps.executeUpdate();
22:    System.out.println(result); // 0
23: }
24:
25: try (var ps = conn.prepareStatement(deleteSql)) {
26: int result = ps.executeUpdate();
27:    System.out.println(result); // 1
28: }

Reading Data with executeQuery()

30: var sql = "SELECT * FROM exhibits";
31: try (var ps = conn.prepareStatement(sql); 32: ResultSet rs = ps.executeQuery() ) { 33:
34: // work with rs
35: }

Processing Data with execute()

boolean isResultSet = ps.execute();
if (isResultSet) {
    try (ResultSet rs = ps.getResultSet()) {
        System.out.println("ran a query");
    }
} else {
    int result = ps.getUpdateCount();
    System.out.println("ran an update");
}

PreparedStatment Methods

Method DELETE INSERT SELECT UPDATE Return Type What is returned for SELECT What is returned for DELETE/INSERT/UPDATE
ps.execute() Yes Yes Yes Yes boolean true false
ps.executeQuery() No No Yes No ResultSet The rows and columns returned n/a
ps.executeUpdate() Yes Yes No Yes int n/a Number of rows added/changed/removed

WORKING WITH PARAMETERS

let op: conn.prepareStatment(sql) VS PreparedStatement ps = conn.prepareStatement(sql)

String sql = "INSERT INTO names VALUES(?, ?, ?)";


public static void register(Connection conn, int key,int type, String name) throws SQLException {

    String sql = "INSERT INTO names VALUES(?, ?, ?)";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
        ps.setInt(1, key);
        ps.setString(3, name);
        ps.setInt(2, type);
        ps.executeUpdate(); // GEEN SQL meesturen!!!
    }
}

Remember that JDBC starts counting columns with 1 rather than 0. A common exam (and interview) question tests that you know this!

When not having all parameters defined: The code compiles, and you get a SQLException. The message may vary based on your database driver.

PreparedStatments Methods

  • setBoolean
  • setDouble
  • setInt
  • setLong
  • setObject (Any type)
  • setString

COMPILE VS. RUNTIME ERROR WHEN EXECUTING

ps.setObject(1, key);
ps.setObject(2, type);
ps.setObject(3, name);
ps.executeUpdate(sql);  // INCORRECT

The problem is that the last line passes a SQL statement. With a PreparedStatement, we pass the SQL in when creating the object. More interesting is that this does not result in a compiler error. Remember that PreparedStatement extends Statement. The Statement interface does accept a SQL statement at the time of execution, so the code compiles. Running this code gives SQLException. The text varies by database.

UPDATING MULTIPLE TIMES

var sql = "INSERT INTO names VALUES(?, ?, ?)";
try (var ps = conn.prepareStatement(sql)) {
   ps.setInt(1, 20);
   ps.setInt(2, 1);
   ps.setString(3, "Ester");
   ps.executeUpdate();

   ps.setInt(1, 21);
   ps.setString(3, "Elias");
   ps.executeUpdate();
}// WORKS!!

BATCHING STATEMENTS

JDBC supports batching so you can run multiple statements in fewer trips to the database. For example, if you need to insert 1,000 records into the database, then inserting them as a single network call (as opposed to 1,000 network calls) is usually a lot faster. You don't need to know the addBatch() and executeBatch() methods for the exam, but they are useful in practice.

   public static void register(Connection conn, int firstKey, int type, String... names) throws SQLException {
      var sql = "INSERT INTO names VALUES(?, ?, ?)";
      var nextIndex = firstKey;
      try (var ps = conn.prepareStatement(sql)) {
         ps.setInt(2, type);
        for(var name: names) {
            ps.setInt(1, nextIndex);
            ps.setString(3, name);
            ps.addBatch();
            nextIndex++;
        }
        int[] result = ps.executeBatch();
        System.out.println(Arrays.toString(result));
      }
}

register(conn, 100, 1, "Elias", "Ester");

Getting Data from a ResultSet

begin met rs.next()

-> 0 leeg
* row1 * row2

daarom: while(rs.next()

String sql = "SELECT id, name FROM exhibits";
Map<Integer, String> idToNameMap = new HashMap<>();

try (var ps = conn.prepareStatement(sql);
    ResultSet rs = ps.executeQuery()) {
    while (rs.next()) {
        int id = rs.getInt("id");
        String name = rs.getString("name");
        idToNameMap.put(id, name);
    }
    System.out.println(idToNameMap);

}
// results: {1=African Elephant, 2=Zebra}

kan ook met index column. LET OP begint met 1!!!

    int id = rs.getInt(1);
    String name = rs.getString(2);

Attempting to access a column name or index that does not exist throws a SQLException, as does getting data from a ResultSet when it isn't pointing at a valid row.

var sql = "SELECT * FROM exhibits where name='Not in table'";
try (var ps = conn.prepareStatement(sql);
   var rs = ps.executeQuery()) {
   rs.next();
   rs.getInt(1); // SQLException
}
// fout: staat rs.next() staaat nog op 0
var sql = "SELECT count(*) FROM exhibits";
try (var ps = conn.prepareStatement(sql);
   var rs = ps.executeQuery()) {
   rs.getInt(1); // SQLException
}
// fout column 0 bestaat NIET
var sql = "SELECT count(*) FROM exhibits";
try (var ps = conn.prepareStatement(sql);
   var rs = ps.executeQuery()) {
   if (rs.next())
      rs.getInt(0); // SQLException
}

var sql = "SELECT name FROM exhibits";
try (var ps = conn.prepareStatement(sql);
   var rs = ps.executeQuery()) {
   if (rs.next())
   rs.getInt("badColumn"); // SQLException
}

To sum up this section, it is important to remember the following: - Always use an if statement or while loop when calling rs.next(). - Column indexes begin with 1.

ResultSet Return from getObject

16: var sql = "SELECT id, name FROM exhibits";
17: try (var ps = conn.prepareStatement(sql);
18:    var rs = ps.executeQuery()) {
19:
20:    while (rs.next()) {
21: Object idField = rs.getObject("id");
22: Object nameField = rs.getObject("name");
23:       if (idField instanceof Integer) {
24:          int id = (Integer) idField;
25:          System.out.println(id);
26: }
27:       if (nameField instanceof String) {
28:          String name = (String) nameField;

USING BIND VARIABLES

Wanneer er vars gezet moeten worden en uitgepakt dan nesten met try.

30: var sql = "SELECT id FROM exhibits WHERE name = ?";
31:
32: try (var ps = conn.prepareStatement(sql)) {
        ps.setString(1, "Zebra");
        try (var rs = ps.executeQuery()) {
           while (rs.next()) {
              int id = rs.getInt("id");
              System.out.println(id);
           }
        }
41: }

Calling a CallableStatement

Sometimes you want your SQL to be directly in the database instead of packaged with your Java code. This is particularly useful when you have many queries and they are complex. A stored procedure is code that is compiled in advance and stored in the database. Stored procedures are commonly written in a database‐specific variant of SQL, which varies among database software providers.

CALLING A PROCEDURE WITHOUT PARAMETERS

A stored procedure is called by putting the word call and the procedure name in braces ( {}).

12: String sql = "{call read_e_names()}";
13: try (CallableStatement cs = conn.prepareCall(sql);
14: ResultSet rs = cs.executeQuery()) {
15:
16:    while (rs.next()) {
17:       System.out.println(rs.getString(3));
18: }
19: }

PASSING AN IN PARAMETER

We have to pass a ? to show we have a parameter. This should be familiar from bind variables with a PreparedStatement.

25: var sql = "{call read_names_by_letter(?)}"; 26: try (var cs = conn.prepareCall(sql)) {
        cs.setString("prefix", "Z");
        try (var rs = cs.executeQuery()) {
           while (rs.next()) {
              System.out.println(rs.getString(3));
           }
        }
}

// => cs.setString(1, "Z"); === cs.setString("prefix", "Z");

RETURNING OUT PARAM

40: var sql = "{?= call magic_number(?) }";
41: try (var cs = conn.prepareCall(sql)) {
42: cs.registerOutParameter(1, Types.INTEGER);
43: cs.execute();
44:    System.out.println(cs.getInt("num"));
45: }

On line 40, we included two special characters ( ?=) to specify that the stored procedure has an output value. This is optional since we have the OUT parameter, but it does aid in readability.

On line 42, we register the OUT parameter. This is important. It allows JDBC to retrieve the value on line 44. Remember to always call registerOutParameter() for each OUT or INOUT parameter (which we will cover next).

WORKING WITH AN INOUT PARAMETER

50: var sql = "{call double_number(?)}"; 51: try (var cs = conn.prepareCall(sql)) {
52: cs.setInt(1, 8);
53: cs.registerOutParameter(1, Types.INTEGER);
54: cs.execute();
55:    System.out.println(cs.getInt("num"));
56: }

COMPARING CALLABLE STATEMENT PARAMETERS

Stored procedure parameter types IN OUT INOUT
Used for input Yes No Yes
Used for output No Yes Yes
Must set parameter value Yes No Yes
Must call registerOutParameter() No Yes Yes
Can include ?= No Yes Yes

Closing Database Resources

it is important to close resources when you are finished with them. This is true for JDBC as well. JDBC resources, such as a Connection, are expensive to create. Not closing them creates a resource leak that will eventually slow down your program.

Closing a JDBC resource should close any resources that it created. In particular, the following are true: - Closing a Connection also closes PreparedStatement (or CallableStatement) and ResultSet. - Closing a PreparedStatement (or CallableStatement) also closes the ResultSet

you learned that it is possible to declare a type before a try‐with‐resources statement. Do you see why this method is bad? ``` 40: public void bad() throws SQLException { 41: var url = "jdbc:derby:zoo"; 42: var sql = "SELECT not_a_column FROM names"; 43: var conn = DriverManager.getConnection(url); 44: var ps = conn.prepareStatement(sql); 45: var rs = ps.executeQuery(); 46: 47: try (conn; ps; rs) { 48: while (rs.next()) 49: System.out.println(rs.getString(1)); 50: } 51: }

Suppose an exception is thrown on line 45. The try‐with‐resources block is never entered, so we don't benefit from automatic resource closing. That means this code has a resource leak if it fails. Do not write code like this.


There's another way to close a ResultSet. JDBC automatically closes a ResultSet when you run another SQL statement from the same Statement. This could be a PreparedStatement or a CallableStatement.

How many are closed?

14: var url = "jdbc:derby:zoo"; 15: var sql = "SELECT count(*) FROM names where id = ?"; 16: try (var conn = DriverManager.getConnection(url); 17: var ps = conn.prepareStatement(sql)) { 18: 19: ps.setInt(1, 1); 20: 21: var rs1 = ps.executeQuery(); 22: while (rs1.next()) { 23: System.out.println("Count: " + rs1.getInt(1)); 24: } 25: 26: ps.setInt(1, 2); 27: 28: var rs2 = ps.executeQuery(); 29: while (rs2.next()) { 30: System.out.println("Count: " + rs2.getInt(1)); 31: } 32: rs2.close();

``` !!!The correct answer is four!!! On line 28, rs1 is closed because the same PreparedStatement runs another query. On line 32, rs2 is closed in the method call. Then the try‐with‐resources statement runs and closes the PreparedSatement and Connection objects.

prev next

Tags: 

Custom Maven Starter Archetype

Create Maven Archetype

  • create an empty Maven project
mvn archetype:generate \
    -DgroupId=nl.appall.java \
    -DartifactId=java \
    -DarchetypeArtifactId=maven-archetype-quickstart \
    -DinteractiveMode=false

  • modify the pom file with for example to add Junit5 and Java 11.
<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>
    <!-- JUnit Jupiter API for writing tests -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.7.0</version>
        <scope>test</scope>
    </dependency>

    <!-- JUnit Jupiter Engine for running tests -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.7.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

  • Use the maven-archetype-plugin to create an archetype from your project:
mvn archetype:create-from-project
  • Deploy the generated archetype to a repository (local or remote) so it can be reused:
mvn install
  • create a Maven Settings file
mkdir -p ~/.m2
echo '<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository/>
  <interactiveMode/>
  <usePluginRegistry/>
  <offline/>
  <pluginGroups/>
  <servers/>
  <mirrors/>
  <proxies/>
  <profiles/>
  <activeProfiles/>
</settings>' > ~/.m2/settings.xml

use your archetype to generate a new project like:

mvn archetype:create-from-project

In bashrc

To automate the creation of a new blank project with your custom archetype you can update your bashrc like ~/.zshrc

march() {
         if [ -z "$1" ]; then
             echo "Error: No project name provided."
             echo "Usage: march <projectname>"
            return 1
        fi

        mvn archetype:generate \
            -DarchetypeGroupId=nl.appall.java \
            -DarchetypeArtifactId=java-archetype \
            -DarchetypeVersion=1.0-SNAPSHOT \
            -DgroupId=nl.appall.newproject \
            -DartifactId=$1 \
            -Dversion=1.0-SNAPSHOT \
            -DinteractiveMode=false

        cd $1
        idea .
   }

now march yourProject creates the project , gets in the folder and starts intellij.

SinglyLinkedList

class Node<T> {
    T data;
    Node<T> next;

    public Node(T data) {
        this.data = data;
        this.next = null;
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        sb.append(data.toString());
        Node<T> current = next;
        while (current != null) {
            sb.append(", ");
            sb.append(current.data.toString());
            current = current.next;
        }
        sb.append("]");
        return sb.toString();
    }
}

public class SinglyLinkedList<T> {
    private Node<T> head;

    public SinglyLinkedList() {
        this.head = null;
    }

    public void add(T data) {
        Node<T> newNode = new Node<>(data);
        if (head == null) {
            head = newNode;
        } else {
            Node<T> current = head;
            while (current.next != null) {
                current = current.next;
            }
            current.next = newNode;
        }
    }

    public boolean remove(T data) {
        if (head == null) {
            return false; // List is empty
        }
        if (head.data.equals(data)) {
            head = head.next;
            return true;
        }

        Node<T> current = head;
        while (current.next != null) {
            if (current.next.data.equals(data)) {
                current.next = current.next.next;
                return true;
            }
            current = current.next;
        }
        return false; // Element not found
    }

    public T get(int index) {
        if (index < 0 || head == null) {
            throw new IndexOutOfBoundsException("Index out of bounds or list is empty.");
        }

        Node<T> current = head;
        int currentIndex = 0;
        while (current != null) {
            if (currentIndex == index) {
                return current.data;
            }
            current = current.next;
            currentIndex++;
        }

        throw new IndexOutOfBoundsException("Index out of bounds.");
    }

    public boolean isEmpty() {
        return head == null;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        Node<T> current = head;
        while (current != null) {
            sb.append(current.data);
            if (current.next != null) {
                sb.append(", ");
            }
            current = current.next;
        }
        sb.append("]");
        return sb.toString();
    }

    public Node middle() {
        if (head == null) {
            return null; // List is empty
        }

        Node<T> slow = head;
        Node<T> fast = head;

        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow;
    }
}

SinglyLinkedList sll = new SinglyLinkedList<Integer>();
System.out.println("sll "+sll);
sll.add(1);
sll.add(2);
sll.add(3);
sll.add(4);
System.out.println("sll "+sll);
var middle = sll.middle();
//System.out.println("middle "+middle.toString());
//sll []
//sll [1, 2, 3, 4]
//middle [3, 4]

Java Curl

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class CurlExample {
    public static void main(String[] args) {
        try {
            // Create a URL object with the desired endpoint
            URL url = new URL("https://api.example.com/some-endpoint");

            // Open a connection to the URL
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            // Set the request method (GET, POST, etc.)
            connection.setRequestMethod("GET");

            // Send the request and receive the response
            int responseCode = connection.getResponseCode();

            // Check if the request was successful (status code 200)
            if (responseCode == HttpURLConnection.HTTP_OK) {
                // Read the response
                BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String line;
                StringBuilder response = new StringBuilder();

                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }

                // Close the reader
                reader.close();

                // Print the response
                System.out.println(response.toString());
            } else {
                System.out.println("Request failed with response code: " + responseCode);
            }

            // Disconnect the connection
            connection.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Cli with java commons

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>testffCli</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>commons-cli</groupId>
            <artifactId>commons-cli</artifactId>
            <version>1.4</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>Cli</finalName>
    </build>
</project>

Cli.java

import org.apache.commons.cli.*;
import java.io.PrintWriter;

public class Cli {
    private static final Option ARG_ADD = new Option("a", "addition", false, "Add numbers together.");
    private static final Option ARG_SUBTRACT = new Option("s", "subtract", false, "Subtracts numbers together.");
    private static final Option ARG_MULTIPLY = new Option("m", "multiply", false, "Multiply numbers together.");
    private static final Option ARG_DIVIDE = new Option("d", "divide", false, "Divide numbers together.");

    public static void printHelp(Options options) {
        HelpFormatter hf = new HelpFormatter();
        PrintWriter pw = new PrintWriter(System.out);
        pw.println("Math App " + Cli.class.getPackage().getImplementationVersion());
        pw.println();
        hf.printUsage(pw, 100, "java -jar Cli.jar [OPTIONS] Number Number");
        hf.printOptions(pw, 100, options, 2, 5);
        pw.close();
    }

    public static void main(String[] args) {

        CommandLineParser clp = new DefaultParser();
        Options options = new Options();
        options.addOption(ARG_ADD);
        options.addOption(ARG_SUBTRACT);
        options.addOption(ARG_MULTIPLY);
        options.addOption(ARG_DIVIDE);

        try {
            CommandLine cl = clp.parse(options, args);

            if (cl.getArgList().size() < 2) {
                printHelp(options);
                System.exit(-1);
            }

            var a = Integer.parseInt(cl.getArgList().get(0));
            var b = Integer.parseInt(cl.getArgList().get(1));

            if (cl.hasOption(ARG_ADD.getLongOpt())) {
                System.out.println(String.format("%1$d + %2$d = %3$d", a, b, (a + b)));
            } else if (cl.hasOption(ARG_SUBTRACT.getLongOpt())) {
                System.out.println(String.format("%1$d - %2$d = %3$d", a, b, (a - b)));
            } else if (cl.hasOption(ARG_MULTIPLY.getLongOpt())) {
                System.out.println(String.format("%1$d * %2$d = %3$d", a, b, (a * b)));
            } else if (cl.hasOption(ARG_DIVIDE.getLongOpt())) {
                System.out.println(String.format("%1$d / %2$d = %3$d", a, b, (a / b)));
            } else {
                printHelp(options);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
Tags: 

Java collections

list.forEach(System.out::print);

Array

int[] a = new int[5];

a[0] = 1;
a[1] = 2;
a[2] = 4;
a[3] = 8;
a[4] = 16;

String => array with .split() & .join()

ArrayList

ordered, index based, dyn sizing, non sync, duplicates allowed

//Non-generic arraylist - NOT RECOMMENDED !!
ArrayList list = new ArrayList();

//Generic Arraylist with default capacity(=16)
List<Integer> numbers = new ArrayList<>(); 

//Generic Arraylist with the given capacity
List<Integer> numbers = new ArrayList<>(6); 

//Generic Arraylist initialized with another collection
List<Integer> numbers = new ArrayList<>( Arrays.asList(1,2,3,4,5) ); 
List<Integer> numbers = new ArrayList<>(6); 
numbers.add(1);

ArrayList<String> charList = new ArrayList<>(Arrays.asList(("A", "B", "C"));
String aChar = alphabetsList.get(0);

ArrayList<Integer> digits = new ArrayList<>(Arrays.asList(1,2,3,4,5,6));

Iterator<Integer> iterator = digits.iterator();

while(iterator.hasNext()) 
{
    System.out.println(iterator.next());
}
for(int i = 0; i < digits.size(); i++) 
{
    System.out.print(digits.get(i));
}
for(Integer d : digits) 
{
    System.out.print(d);
}

sorting

public class AgeSorter implements Comparator<Employee> 
{
    @Override
    public int compare(Employee e1, Employee e2) {
        //comparison logic
    }
}

link: https://howtodoinjava.com/java-collections/

Spring Mysql One to Many....

pom.xml
- lombok
- spring-boot-starter-data-jpa
- mysql-connector-java

application.properties

# MySQL connection properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.url=jdbc:mysql://localhost:3306/testspring

# Log JPA queries
# Comment this in production
spring.jpa.show-sql=true

# Drop and create new tables (create, create-drop, validate, update)
# Only for testing purpose - comment this in production
spring.jpa.hibernate.ddl-auto=create-drop

# Hibernate SQL dialect
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

ResApplication.java

public class RestApplication {

    public static void main(String[] args) {
        SpringApplication.run(RestApplication.class, args);
    }

    @Bean
    public CommandLineRunner mappingDemo(BookRepository bookRepository,
                                         PageRepository pageRepository) {
        return args -> {

            // create a new book
            Book book = new Book("Java 101", "John Doe", "123456");

            // save the book
            bookRepository.save(book);
            pageRepository.save(new Page(65, "Java 8 contents", "Java 8", book));
            pageRepository.save(new Page(95, "Concurrency contents", "Concurrency", book));
        };
    }
}

Book.java


@Entity @Table(name = "books") @Getter @Setter public class Book implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String author; @Column(unique = true) private String isbn; ```**```@JsonManagedReference```**``` @OneToMany(mappedBy = "book", fetch = FetchType.LAZY, cascade = CascadeType.ALL) private List<Page> pages = new ArrayList<Page>(); public Book() { } public Book(String title, String author, String isbn) { this.title = title; this.author = author; this.isbn = isbn; } // getters and setters, equals(), toString() .... (omitted for brevity) @Override public String toString() { return "Book{" + "id=" + id + ", title='" + title + '\'' + ", author='" + author + '\'' + ", isbn='" + isbn + '\'' + ", number of pages=" + pages.size() + '}'; } }

Pages.java


@Entity @Table(name = "pages") @Setter @Getter public class Page implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private int number; private String content; private String chapter; @JsonBackReference @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "book_id", nullable = false) private Book book; public Page() { } public Page(int number, String content, String chapter, Book book) { this.number = number; this.content = content; this.chapter = chapter; this.book = book; } // getters and setters, equals(), toString() .... (omitted for brevity) @Override public String toString() { return "Page{" + "id=" + id + ", number=" + number + ", content='" + content + '\'' + ", chapter='" + chapter + '\'' + ", book=" + book.toString() + '}'; } }

BookRepository.java

public interface BookRepository extends CrudRepository<Book, Long> {

    Book findByIsbn(String isbn);
}

PageRepository.java

public interface PageRepository extends CrudRepository<Page, Long> {

    List<Page> findByBook(Book book, Sort sort);
}

BooksController.java


@RestController @RequestMapping(value = "/books", produces = MediaType.APPLICATION_JSON_VALUE) public class BooksController { private final BookRepository bookRepository; private final PageRepository pageRepository; public BooksController(BookRepository bookRepository, PageRepository pageRepository) { this.bookRepository = bookRepository; this.pageRepository = pageRepository; } @GetMapping(value = "/pages", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) public List<Page> pages(){ List<Page> result = (List<Page>) pageRepository.findAll(); System.out.println(result); System.out.println(result.get(0)); return result; } @GetMapping("/") public List<Book> books(){ return (List<Book>)bookRepository.findAll(); } }

http://localhost:8080/books/

[
{
"id": 1,
"title": "Java 101",
"author": "John Doe",
"isbn": "123456",
"pages": [
{
"id": 1,
"number": 1,
"content": "Introduction contents",
"chapter": "Introduction"
},
{
"id": 2,
"number": 65,
"content": "Java 8 contents",
"chapter": "Java 8"
},
{
"id": 3,
"number": 95,
"content": "Concurrency contents",
"chapter": "Concurrency"
}
]
}
]

http://localhost:8080/books/pages

[
{
"id": 1,
"number": 1,
"content": "Introduction contents",
"chapter": "Introduction"
},
{
"id": 2,
"number": 65,
"content": "Java 8 contents",
"chapter": "Java 8"
},
{
"id": 3,
"number": 95,
"content": "Concurrency contents",
"chapter": "Concurrency"
}
]

Javafx

Download the appropriate JavaFX SDK for your operating system and unzip it to a desired location, for instance /Users/your-user/Downloads/javafx-sdk-11.

Create a JavaFX project

Create a JavaFX project Provide a name to the project, like HelloFX, and a location. When the project opens, the JavaFX classes are not recognized. enter image description here

Set JDK 11

Go to File -> Project Structure -> Project, and set the project SDK to 11. You can also set the language level to 11. Set JDK 11 enter image description here

Create a library

Go to File -> Project Structure -> Libraries and add the JavaFX 11 SDK as a library to the project. Point to the lib folder of the JavaFX SDK. enter image description here

Once the library is applied, the JavaFX classes will be recognized by the IDE. enter image description here

Warning: If you run now the project it will compile but you will get this error:

Error: JavaFX runtime components are missing, and are required to run this application

This error is shown since the Java 11 launcher checks if the main class extends javafx.application.Application. If that is the case, it is required to have the javafx.graphics module on the module-path.

Add VM options

To solve the issue, click on Run -> Edit Configurations... and add these VM options:

--module-path %PATH_TO_FX% --add-modules=javafx.controls,javafx.fxml

Note that the default project created by IntelliJ uses FXML, so javafx.fxml is required along with javafx.controls. If your project uses other modules, you will need to add them as well. enter image description here Click apply and close the dialog.

Run the project

Click Run -> Run... to run the project, now it should work fine.

Alternative Maven archetype way

mvn archetype:generate \
        -DarchetypeGroupId=org.openjfx \
        -DarchetypeArtifactId=javafx-archetype-simple \
        -DarchetypeVersion=0.0.3 \
        -DgroupId=org.openjfx \
        -DartifactId=sample \
        -Dversion=1.0.0 \
        -Djavafx-version=23.0.1

maven start

<properties>
      <maven.compiler.source>1.14</maven.compiler.source>
      <maven.compiler.target>1.14</maven.compiler.target>
   </properties>

Tags: 

random int between

private int randInt(int min, int max) {

  Random rand = new Random();
  int randomNum = rand.nextInt((max - min) + 1) + min;

  return randomNum;
}

Tags: 
Subscribe to RSS - java