preface
Reading such a headline, the first reaction is not that of the “slag Hui” wielding a machete on a computer screen as Louis koo says, “This is a brand new version you’ve never played before.” Of course, this is not clickbait, since “this is the new version you’ve never played before”, then “if you are a brother, read my article”. (If you’re not a brother, why so!)
background
When I was developing the function of an old project that had not been updated and maintained for hundreds of years, I had to try my best to explore the system architecture and corresponding business functions of the project from some codes and notes, because the people who had developed the project had gone their separate ways and left no visible documents. This is definitely a pain in the neck (looking at someone else’s code is like smelling SHI compared to looking at your own code from a few months ago). Read a few times down, most of the code written are ugly, even the basic formatting code is not, but there is a section of code has attracted my attention, but also because of this section of code just caused my thinking, is also have this article.
What kind of code is that
Without further ado, go directly to the code:
new AbstractSpinBusiness() {
@Override
protected boolean handle(a) {
CompanyProfile updateProfile = getProfileForUpdateConf(staff, attrMap);
return companyProfileDao.updateCompanyProfileConf(updateProfile) > 0;
}
}.spin(5);
Copy the code
To recap, this means executing the method in handle() with a limit of 5 retries. Then we could look at AbstractSpinBusiness this abstract class AbstractSpinBusiness. Class
import org.apache.log4j.Logger;
public abstract class AbstractSpinBusiness {
private Logger logger = Logger.getLogger(this.getClass());
public void spin(int times){
for(int i = 0; i < times; i++){
if(handle()){
return;
}
logger.debug(String.format("Spin retry % s", i)); }}/** * executor *@returnTrue: The command is executed successfully. False: the command is executed successfully. */
protected abstract boolean handle(a);
}
Copy the code
It seems that this is just an implementation of the Handle in AbstractSpinBusiness and applies it to the spin method. If you are careful, you may have noticed the design pattern using templates. If you can, give you a thumbs-up. If not, that’s fine, because I’m not here to talk about template design patterns. Of course, the template design pattern is also very important and excellent writing method, in abstract business, architecture is used everywhere!
So is this an elegant way to write it
Recall that when we first learned JDBC, we needed to manually obtain the Connection, set parameters in a PreparedStatement, and then repackage the object into the data format we wanted. After these operations, we can find that, What is really appropriate with the business is just that SQL, and the other work is just an auxiliary work.
Therefore, ORM frameworks such as MyBatis and Hibernate are born. Developers need only care about our own business when using these frameworks. For example, in MyBatis, we only need to write the BUSINESS SQL and the corresponding Mapper, which can be integrated into the Service of the business, while other operations have been encapsulated in the framework without perceptual execution.
Let’s go back to the code we started with. It’s not hard to see the same idea behind this simple code, which is to separate business code from non-business code. If similar retry logic is needed somewhere else, would you want to write a set of retry code?
extension
Along these lines, the same notation was used in a recent optimization. At present, there are some slow SQL in the system, SQL itself is relatively simple, there is no problem in EXPLAIN execution plan, just simple rows more. The problem is that services generate a large number of parameters, resulting in too much data in THE IN, resulting in low SQL execution efficiency. So the more direct optimization is to control the parameters here and do batch processing. (Of course, we don’t consider subqueries or eq_range_INDEx_dive_limit.)
How would you write it
Without the introduction, I think your code would look something like this
import com.google.common.collect.Lists;
import java.util.List;
public class Test {
public static final int PARTITION_SIZE = 1000;
public static void main(String[] args) {
// The simulation is a business parameter
List<Long> paramIds = Lists.newArrayList(1L.2L.3L);
// Delimit
List<List<Long>> partitionParamIds = Lists.partition(paramIds, PARTITION_SIZE);
List<Object> resultList = Lists.newArrayListWithExpectedSize(paramIds.size());
partitionParamIds.forEach(partition -> {
// Perform specific DAO operations, which are also simulated here
resultList.addAll(newObjectDao().getList(partition)); }); System.out.println(resultList.size()); }}class ObjectDao {
// Don't be picky
public List<Object> getList(List<Long> paramIds) {
List<Object> resultList = Lists.newArrayList();
for (Long paramId : paramIds) {
resultList.add(paramId);
}
returnresultList; }}Copy the code
In fact, such writing itself is relatively simple and neat, but have you noticed that the action of slicing here is mixed with the specific business code, so it cannot make clear responsibilities in a certain sense? Is it possible to provide a helper class to do these things? So let me rewrite this.
import com.google.common.collect.Lists;
import java.util.List;
public class Test {
public static void main(String[] args) {
// The simulation is a business parameter
List<Long> paramIds = Lists.newArrayList(1L.2L.3L);
List<Object> resultList = new CommonDoPartition<>().partitionToQuery(paramIds,
partition -> newObjectDao().getList(partition)); System.out.println(resultList.size()); }}class ObjectDao {
// Don't be picky
public List<Object> getList(List<Long> paramIds) {
List<Object> resultList = Lists.newArrayList();
for (Long paramId : paramIds) {
resultList.add(paramId);
}
returnresultList; }}Copy the code
CommonDoPartition is a class that implements commondopartition.java
import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import java.util.List;
import java.util.function.Function;
public class CommonDoPartition<T> {
private final static Logger logger = Logger.getLogger(CommonDoPartition.class);
public static final int PARTITION_SIZE = 1000;
public <T, R> List<R> partitionToQuery(int partitionSize, List<T> all, Function<List<T>, List<R>> function) {
if (CollectionUtils.isEmpty(all)) {
logger.warn("no data to query");
return Lists.newArrayList();
}
List<List<T>> partitions = Lists.partition(all, partitionSize);
List<R> result = Lists.newArrayList();
for (List<T> list : partitions) {
List<R> resultList = function.apply(list);
if (!CollectionUtils.isEmpty(resultList)) {
result.addAll(resultList);
}
}
return result;
}
public <T, R> List<R> partitionToQuery(List<T> all, Function<List<T>, List<R>> function) {
return this.partitionToQuery(PARTITION_SIZE, all, function); }}Copy the code
As you can see, the sharding operation is placed in this public method, so the business side just needs to support the business logic it wants with lambda expressions, and the non-business work can be done by the utility class.
Let me expand it a little bit
Now that you’ve implemented the query operation, you can also do the execute operation
public void partitionToDo(int partitionSize, List<T> all, Consumer<List<T>> consumer) {
if (CollectionUtils.isEmpty(all)) {
logger.warn("no data to consume");
return;
}
List<List<T>> partitions = Lists.partition(all, partitionSize);
for(List<T> list : partitions) { consumer.accept(list); }}public void partitionToDo(List<T> all, Consumer<List<T>> consumer) {
this.partitionToDo(PARTITION_SIZE, all, consumer);
}
public <T, R> void partitionToQueryAndDo(int partitionSize, List<T> all, Function<List<T>, List<R>> function, Consumer<List<R>> consumer) {
if (CollectionUtils.isEmpty(all)) {
logger.warn("no data to consume");
return;
}
List<List<T>> partitions = Lists.partition(all, partitionSize);
List<R> resultList;
for(List<T> list : partitions) { resultList = function.apply(list); consumer.accept(resultList); }}public <T, R> void partitionToQueryAndDo(List<T> all, Function<List<T>, List<R>> function, Consumer<List<R>> consumer) {
this.partitionToQueryAndDo(PARTITION_SIZE, all, function, consumer);
}
Copy the code
PartitionToDo performs tasks in batches. PartitionToQueryAndDo combines the previous batch query with some data and performs operations on the data. These are all examples that can be combined.
At the end
Of course, I also have limited ability, and can not abstract, combine and summarize most of the scenes. I just think that in daily development, only writing business and only writing code, without my own thinking and those excellent design ideas and concepts, I still cannot achieve the degree of self-improvement. This article is just my work in some small thinking, but also a little summary, only for your reference.