Today, I mainly solved the verification of the login status of the test account. My current solution is to use the special syntax uid=123 when writing the test case, which means that the field of the use case should go to the login credentials of the test account with uid equal to 123. The difficulty is that the login credentials will expire and will be squeezed out. If it is troublesome to maintain the login status of all test users, it is also a waste of server performance.
So the final plan is as follows:
- A. In the process of debugging a single use case, the validity period of a voucher is reserved. After the validity period expires, the validity of the voucher will continue to be verified. If it succeeds, the validity period will be reset. If it fails, the user's login voucher will be retrieved from the login interface and the validity period will be updated
- B. In order to ensure the validity of login credentials of each test user, only one thread is allowed to execute the logic of A at A time. The user credentials are cached during the run of the use case set so that they are not repeatedly read from the database.
When running test cases in parallel, if you want to ensure that all threads can read the latest user credentials, and that the data read to the cache map is correct, you have a plan to lock each user in the JVM, so that only one thread can read and write the user login credentials at a time. In this way, when a single user is running, he can log in once, and debug the use case continuously without logging in for a short time. When running the use case set, each time a temporary map is created to store the user login credentials used this time. After the test cases in the use case set are executed, the object is released and waiting to be recycled by the GC.
User object lock storage class
- Here we use the previous knowledge: Application of Lambda expression in thread safe Map.
package com.okay.family.common.basedata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.ConcurrentHashMap; public class UserCertificate { private static Logger logger = LoggerFactory.getLogger(UserCertificate.class); private static ConcurrentHashMap<Integer, Object> certificates = new ConcurrentHashMap<>(); /** * Get lock object, user test user lock * * @param id * @return */ public static Object get(int id) { certificates.compute(id, (key, value) -> { if (value == null) { value = new Object(); } return value; }); return certificates.get(id); } }
Business implementation class
@Override @Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRES_NEW) public TestUserCheckBean getCertificate(int id) { Object o = UserCertificate.get(id); synchronized (o) { TestUserCheckBean user = testUserMapper.findUser(id); String create_time = user.getCreate_time(); long create = Time.getTimestamp(create_time); long now = Time.getTimeStamp(); if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode()) return user; boolean b = UserUtil.checkUserLoginStatus(user); if (!b) { UserUtil.updateUserStatus(user); } testUserMapper.updateUserStatus(user); return user; } } @Override public String getCertificate(int id, ConcurrentHashMap<Integer, String> map) { Object o = UserCertificate.get(id); synchronized (o) { if (map.contains(id)) return map.get(id); TestUserCheckBean user = testUserMapper.findUser(id); String create_time = user.getCreate_time(); long create = Time.getTimestamp(create_time); long now = Time.getTimeStamp(); if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode()) { map.put(id, user.getCertificate()); return user.getCertificate(); } boolean b = UserUtil.checkUserLoginStatus(user); if (!b) { UserUtil.updateUserStatus(user); if (user.getStatus()!=UserState.OK.getCode()) UserStatusException.fail(); } map.put(id, user.getCertificate()); testUserMapper.updateUserStatus(user); return user.getCertificate(); } }
- The setting of transaction isolation level and transaction propagation behavior is used here, because it was intended to achieve the effect in this way, but later it was found that it is not possible, because updating user login credentials involves reading and writing database several times, and multithreading processing will definitely generate BUG.
Attach documentation of transaction isolation level and propagation behavior
Transaction isolation level
Isolation level refers to the isolation level between several concurrent transactions. The main scenarios related to our development include dirty reading, repeated reading and unreal reading.
- DEFAULT: This is the DEFAULT value, indicating that the DEFAULT isolation level of the underlying database is used. For most databases, this value is usually: READ_COMMITTED .
- READ_UNCOMMITTED: this isolation level indicates that one transaction can read data modified by another transaction but not yet committed. This level does not prevent dirty and non repeatable reads, so it is rarely used.
- READ_COMMITTED: this isolation level indicates that one transaction can only read data that has been committed by another transaction. This level prevents dirty reading, which is also recommended in most cases.
- REPEATABLE_READ: this isolation level indicates that a transaction can execute a query repeatedly in the whole process, and the records returned are the same each time. Even if there is new data between multiple queries to satisfy the query, these new records will be ignored. This level prevents dirty and non repeatable reads.
- SERIALIZABLE: all transactions are executed one by one, so that there is no interference between transactions. That is to say, this level can prevent dirty reading, non repeatable reading and unreal reading. But this will seriously affect the performance of the program. This level is not normally used.
Communication behavior
The so-called transaction propagation behavior refers to that if a transaction context already exists before starting the current transaction, there are several options to specify the execution behavior of a transactional method.
- REQUIRED: if there is a transaction, join it; if there is no transaction, create a new transaction.
- SUPPORTS: if there is a transaction, join it; if there is no transaction, continue to run in a non transactional way.
- MANDATORY: if there is currently a transaction, the transaction will be added; if there is no current transaction, an exception will be thrown.
- REQUIRES_NEW: create a new transaction. If there is a current transaction, suspend the current transaction.
- NOT_SUPPORTED: runs in a non transactional mode. If there is a current transaction, the current transaction will be suspended.
- NEVER: run in non transactional mode, throw an exception if there is currently a transaction.
- NESTED: if there is currently a transaction, create a transaction to run as a NESTED transaction of the current transaction; if there is no transaction currently, the value is equivalent to REQUIRED.
- The official account is that the public number "FunTester" starts, and welcomes attention and prohibits the third party from reprinting. More original articles: FunTester 18 original albums , please contact Fhaohaizi@163.com .