0

I'm writing java console app using spring data jpa and mysql and I'm trying to solve the following situation:

App generates new object every second which should be saved to db at the same moment in the same order. If db connection will be lost or timeout exception will be invoked during object saving a long time then save upcoming objects to temporary buffer. When connection will be recovered, save all these accumulated objects and new upcoming generated object(in that particular moment) to db.

My questions are:

  • How I can handle to save object in temp buffer when db connection lost?

I guess I should caught Throwable using ScheduledExecutorService when db connection lost and then save the particular object to CopyOnWriteArrayList, is it correct way?

  • How I can stop saving new objects to db when connection lost ot timeout exception was invoked by previous object saving and resume process of saving upcoming objects when connection is up?
  • How I can save all accumulated objects to db when connection is up before saving new coming generated objects?

UPDATE

I wrote the service which runs objects generation with above mentioned behavior:

Service

@Service
public class ReportService implements IReportService {

  @Autowired
  private ReportRepository reportRepository;

  @Override
  public void generateTimestamps() {

    BlockingQueue<Report> queue = new LinkedBlockingQueue<>();
    new Thread(new ReportsProducer(queue)).start();
    new Thread(new ReportsConsumer(queue, reportRepository)).start();
  }

  @Override
  public List<Report> showTimestamps() {
    return reportRepository.findAll();
  }
}

Object Producer:

public class ReportsProducer implements Runnable {

  private final BlockingQueue<Report> reportsQueue;

  ReportsProducer(BlockingQueue<Report> numbersQueue) {
    this.reportsQueue = numbersQueue;
  }

  public void run() {
    try {
      while (true) {
        generateReportEverySecond();
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }

  private void generateReportEverySecond() throws InterruptedException {
    Thread.sleep(1000);
    Report report = new Report();
    reportsQueue.put(report);
    System.out.println(Thread.currentThread().getName() + ": Generated report[id='" + report.getId() + "', '" + report
            .getTimestamp() + "']");
  }
}

Object consumer:

public class ReportsConsumer implements Runnable {
  private final BlockingQueue<Report> queue;

  private ReportRepository reportRepository;

  ReportsConsumer(BlockingQueue<Report> queue, ReportRepository reportRepository) {
    this.queue = queue;

//    Not sure i do this correct way
    this.reportRepository = reportRepository;
  }

  public void run() {
    while (true) {
      try {
        if (!queue.isEmpty()) {
          System.out.println("Consumer queue size: " + queue.size());

          Report report = reportRepository.save(queue.peek());
          queue.poll();
          System.out.println(Thread.currentThread().getName() + ": Saved report[id='" + report.getId() + "', '" + report
                  .getTimestamp() + "']");
        }

      } catch (Exception e) {

        //  Mechanism to reconnect to DB every 5 seconds
        try {
          System.out.println("Retry connection to db");
          Thread.sleep(5000);
        } catch (InterruptedException e1) {
          e1.printStackTrace();
        }
      }
    }
  }
}

Repository:

@Repository
public interface ReportRepository extends JpaRepository<Report, Long> {
}

Object:

@Entity
@Table(name = "reports")
public class Report {

  @Id
  @GeneratedValue
  private Long id;

  @Column
  private Timestamp timestamp;

  public Report() {
    this.timestamp = new Timestamp(new Date().getTime());
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public Timestamp getTimestamp() {
    return timestamp;
  }

  public void setTimestamp(Timestamp timestamp) {
    this.timestamp = timestamp;
  }
}

Application.properties:

spring.datasource.url=jdbc:mysql://xyz:3306/xyz
spring.datasource.username=xyz
spring.datasource.password=xyz

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.properties.hibernate.connection.driver_class=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.ddl-auto = create-drop

# Below properties don't work actually
spring.jpa.properties.javax.persistence.query.timeout=1
# Reconnect every 5 seconds
spring.datasource.tomcat.test-while-idle=true
spring.datasource.tomcat.time-between-eviction-runs-millis=5000
spring.datasource.tomcat.validation-query=SELECT 1

build.gradle:

version '1.0'

buildscript {
    ext {
        springBootVersion = '1.5.9.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

sourceCompatibility = 1.8
targetCompatibility = 1.8

ext {
    mysqlVersion = '6.0.6'
    dbcp2Version = '2.2.0'
    hibernateVersion = '5.2.12.Final'
}

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'mysql', name: 'mysql-connector-java', version: mysqlVersion
    compile group: 'org.hibernate', name: 'hibernate-core', version: hibernateVersion
    compile group: 'org.hibernate', name: 'hibernate-c3p0', version: hibernateVersion
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    compile group: 'org.assertj', name: 'assertj-core', version: '3.9.0'
}

Based on above code,I would like to know some moments:

  • How you recommend to check db connectivity?

    I use save operation to check, if db connection lost I just wait 5 sec and repeat the operation. Moreover, datasource is configured to stand up db connection every 5 sec if it's down. Is there more correct way to do it?

  • What will happen if db connection is active but db is very slow or too busy(ovberloaded) at the moment?

    As I understand I need to set timeout for query. What else I should undertake in this scenario?

Viktor M.
  • 4,393
  • 9
  • 40
  • 71
  • 1
    How about sending all the objects to a queue and having another piece of code/module to dequeue the objects and insert them into db? When the db is down, the queue is the buffer you are looking for and your dequeue-insertion module will stop work until the connection comes back. – Top.Deck Jan 09 '18 at 18:36
  • Good point, as I understand I should use ConcurrentLinkedDeque to provide and consume my objects between threads. Is it correct way? – Viktor M. Jan 09 '18 at 19:41
  • It depends on the scale of your project. For light projects, an in-memory queue/deque is good enough. If you are working on a large-scale project, you may want to use an message queue framework. – Top.Deck Jan 09 '18 at 20:06
  • Thank you Top.Deck! You show me what to look at. Could you give me some tips to my new questions in the bottom of the topic? – Viktor M. Jan 10 '18 at 01:58
  • Any refactoring suggestions are welcome as well=) – Viktor M. Jan 10 '18 at 01:59

2 Answers2

2

For the first three questions, you are looking for a Queue. If you are using Spring framework, it does use a single class for JPA since Repository is a Bean and Spring will handle the DB connection pool for you as long as you configure it correctly.

  1. How you recommend to check db connectivity?

    What you did in application.properties does check the connectivity. I think 5000 milliseconds is too frequent, it may slow down your system. 360000 could be a good interval.

  2. What will happen if db connection is active but db is very slow or too busy(ovberloaded) at the moment?

    When you configure your connection pool, you could setup the following properties:

    removeAbandoned - set to true if we want to detect leaked connections

    removeAbandonedTimeout - the number of seconds from when dataSource.getConnection was called to when we consider it abandoned

    logAbandoned - set to true if we should log that a connection was abandoned. If this option is set to true, a stack trace is recorded during the dataSource.getConnection call and is printed when a connection is not returned

Reference: Configuring jdbc-pool for high-concurrency

Top.Deck
  • 1,077
  • 3
  • 16
  • 31
  • About db connection check, I have figured out that defined properties don't work actually(i have checked it by closing internet connection during app running and when I turn internet connection back db connection appeared immediately i.e. less than few seconds) and cannot understand why. Firstly, I think I named properties wrong, but didn't figured out what actually wrong with them or with my environment. What the problem is that? – Viktor M. Jan 10 '18 at 16:02
  • Are you using Spring or Spring-Boot? T̶r̶y̶ ̶s̶p̶r̶i̶n̶g̶.̶d̶a̶t̶a̶s̶o̶u̶r̶c̶e̶.̶t̶i̶m̶e̶-̶b̶e̶t̶w̶e̶e̶n̶-̶e̶v̶i̶c̶t̶i̶o̶n̶-̶r̶u̶n̶s̶-̶m̶i̶l̶l̶i̶s̶ ̶i̶n̶s̶t̶e̶a̶d̶ ̶o̶f̶ ̶s̶p̶r̶i̶n̶g̶.̶d̶a̶t̶a̶s̶o̶u̶r̶c̶e̶.̶t̶o̶m̶c̶a̶t̶.̶t̶i̶m̶e̶-̶b̶e̶t̶w̶e̶e̶n̶-̶e̶v̶i̶c̶t̶i̶o̶n̶-̶r̶u̶n̶s̶-̶m̶i̶l̶l̶i̶s̶.̶ – Top.Deck Jan 10 '18 at 16:19
  • I'm using Spring-Boot. I have tried spring.datasource.time-between-eviction-runs-millis and spring.datasource.tomcat.time-between-eviction-runs-millis - no effect. Db connection appears immediately when I turn on internet connection. – Viktor M. Jan 10 '18 at 17:46
  • @user1376885 DB connection should appear right after your internet connection comes back if you sql is on a remote host. What do you expect? – Top.Deck Jan 10 '18 at 18:05
  • mysql on remote server. I thought that app will try to connect to db after 5 second from the moment internet connection appearance but not immediately. I quess it was wrong assumption. How I can emulate the situation when app will try connect to db only after 5 seconds? – Viktor M. Jan 10 '18 at 18:57
  • And I would like to emulate the situations when db is slow or overloadedto check removeAbandoned properties in my connection pool, any suggestions how to do it? – Viktor M. Jan 10 '18 at 18:58
  • I guess the way you test is right after your internet connection comes back, your code will try to acquire a db connection such as a select statement which will tell the pool to re-connect to your DB. The 5 seconds interval means for every 5 seconds after the connection pool becomes idle, the framework will try to send SELECT 1 to DB as a dummy query to check the connectivity. – Top.Deck Jan 10 '18 at 19:23
  • I see, thank you Top.Deck for you help. Maybe, you can look on another problem - https://stackoverflow.com/questions/48198461/run-spring-boot-unit-tests-ignoring-commandlinerunner – Viktor M. Jan 11 '18 at 10:15
0

IMO, checking database connection every 5 sec should not be a good idea instead you are overloading your database with some unnecessary request. As @TopDeck mentioned instead you should use Queue implementation with connection pooling (you should not overload database by creating new connection for add new object)

Ravi
  • 30,829
  • 42
  • 119
  • 173
  • But we try to check db connection by query(useful query, which should be executed when db connection will be recovered anyway) only when connection is abandoned. There is just Thread.sleep(5000) in the code which is a mechanism to retry object saving after 5 sec. Only datasource is configured to recover abandoned connection every 5 sec. Is it still not recommended way? If it's so could you explain what configurations or piece of code I should you change? – Viktor M. Jan 10 '18 at 08:22
  • @user1376885 are you aware of singleton pattern ?? use that to make sure, if you will always have one instance for db connection. Now, If you are certain that, in every 5 sec, you need to perform db activity, then I would suggest to open connection only once instead opening for every request – Ravi Jan 10 '18 at 09:20
  • @user1376885 I would also suggest to look consumer-producer design pattern, which is suitable in your scenario. It will better to understand the use case/design pattern and write it yourself. At the end of this, you will feel, you learnt something otherwise. It will be copy paste as usual – Ravi Jan 10 '18 at 09:23
  • Yes, I'm familiar with singleton pattern. If spring data jpa doesn't wrap db connection instance into singleton? If not, what db connection implementation I should put into singleton? – Viktor M. Jan 10 '18 at 10:18
  • @user1376885 I haven't worked on spring, so I can't comment on its framework and related implementation – Ravi Jan 10 '18 at 11:15