The articles

  • “Graduation Design from Scratch” -ELS Express Logistics Scheduling System in Taiwan (I) Building project environment – Digging Gold (Juejin. Cn)
  • “Graduation Design from Scratch” -ELS Zhongtai Express Logistics Scheduling system (II) Basic order function – Juejin (JUejin.cn)

Warehouse Address: Ashlee618 /ELS Express Logistics Scheduling System (github.com)

Leaning back

In the last article, we built the basic logistics order capability and integrated Mybatis- Plus to complete the basic CRUD function. In this article, we will complete the basic functions of the logistics address. Complete building level 4 address capability and basic CRUD, using local cache Caffeine.

Logistics address capability

Make sure the address is valid

In the past, when I designed the address of the project, I filled in the address written by the user directly. In fact, this is also very irresponsible.

If the user were to fill in an invalid address like 124 Conch Street, Bikini Beach, Pacific, then there would be unnecessary disputes


Merchant: I am only responsible for the delivery, how to deliver to the customer’s hands, but I have nothing to do with ao

Express company: I’m just responsible for packing and distributing the package. How to deliver the package is the responsibility of the Courier. It’s not my business

Courier:??


So when we’re upstream, we check the shipping address to make sure it’s valid.

A simple approach

For provinces, cities, districts and streets, we can directly use constants to fix them, and users can only select them through the drop-down box. And then one more field to the user to fill in the detailed address, such as the community name, building, number and so on.

But there’s a problem with this approach:

Districts under different provinces may have the same name

For example, Chaoyang district of Beijing and Chaoyang District of Changchun.

To solve this problem, addresses must carry provinces, cities, districts, and streets to narrow the problem. But it also raises other questions

bandwidth

  • Transmission level: the efficiency of direct transmission of Chinese characters is certainly lower than transmission of numbers, characters, etc., and some places of the address is very long, very long, not appropriate.

  • At the level of concurrency: Logistics address is a module with frequent concurrency. Orders, logistics information and physical service modules all need to read the address.

Level 4 address

The four-level address refers to province, city, county and street. Constitute four levels.

We can see that each level of address has its own code, and the code of the next level of address is transferred from the previous level. So we only need to know the address of a certain level, and we can deduce the address of the next level.

Then I went to the online area to find the national level four address, found that this thing is really not easy to find, basic are in a DN, to integral download

Finally I found one, and found that the text format alone accounted for 7.62MB, when opened will be jammed

The most speechless is that his format is not the original author of that, name, code, level, only name and code

Just when I felt I was about to send it, I found the treasure! The old way didn’t work, so we used something else instead.

I just found another database that didn’t overlap with the original author’s table, but could be used,

! [image. PNG] (p6-juejin.byteimg.com/tos-cn-i-k3… fbf33030c6~tplv-k3u1fbpfcp-watermark.image?)

Ok, so the level 4 address data is finished here

CRUD

CRUD is also integrated with Mybatis- Plus, which was introduced in the last article, and will not be covered in the future if there are no more details.

Read more to write less

The national address basically doesn’t change, except for the discovery of a certain hideaway, or an important historical event, so the data basically doesn’t change, so it’s a type of read more than write less.

Common solutions to query stress are database level and cache level

The database

The index

The related fields to establish a joint index, do not let him go back to the table, set up the index, but also their explain execution plan to check, is not the last to go to the index, if not, you can use force index to let him forcibly go to the index.

From more than a master

One master writes, and multiple slaves relieve the pressure of reading. Since I only have one Ali Cloud server, it is quite convenient to use Dokcer to deploy Mysql cluster.

Table depots

You can shard from both vertical and horizontal dimensions, mock your own 100,000-level data, and then slice it according to a strategy, perhaps using sharing-JDBC or some other repository and table middleware. This step is reserved for later optimization. For now, the basic capabilities are set up first.

The cache

Cache we generally think of Redis, but we have to specific problems specific analysis, if run away from the business, talk about technology, is playing rogue.

Level 4 addresses are used throughout the business process from ordering to receiving, so how do you maximize QPS when designing

We know that cache efficiency is local cache > remote cache > database.

One of the downsides of local caching is that it can’t store too much, which can cause heap memory leaks and so on. But his advantage is also very obvious, that is, he is very fast! Therefore, in some special scenes, such as seckilling, grabbing red packets and other high concurrency and data volume is not too large, the technology selection, can choose local cache + Redis to form a secondary cache.

Good local Cache frameworks include Guava’s Cache, Apache’s LRUMap, Caffeine, Edcache, and more

When springboot2.0 abandoned Guava’s Cache and switched to Caffeine, we need to do the same with Caffeine!

Caffein local cache

Caffeine is cache-based and operates very similarly, making it easy to move from Cache to Caffeine. It can be understood that the former is an extended version of the latter, with richer built-in functions, such as expulsion policies, listeners, statistics, and so on.

Rely on

I used version 2.6.2

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>${caffeine.version}</version>
</dependency>
Copy the code

CaffeineService

I have tried to use @bean to inject Caffeine into Mybatis- Plus, but it is not very good. I guess it is because the Bean in Mybatis- Plus has not been loaded yet, so I cannot find Caffeine. In the example, the bean is also obtained manually after SpringUtil. Let’s just say that Caffeine encapsulates a suit.

/** * Caffeine Local cache *@authorHongMinFeng * /
@Component
public class CaffeineService {
 
    private Cache<Object, Object> cache;
 
    public CaffeineService(a) {
        this.cache = Caffeine.newBuilder()
                // Expiration policy: If no data is read within one minute, the data will expire and be deleted
                .expireAfterWrite(1, TimeUnit.MINUTES)
                If the number exceeds 100, the number will be automatically deleted
                .maximumSize(100)
                .build();
    }

    public Object put(Object key, Object value) {
        cache.put(key, value);
        return key;
    }

    public Object getIfPresent(Object key) {
        return cache.getIfPresent(key);
        // If you can't find it in the local cache, go to Redis
        // By default, the local cache cannot be found in the database because it is integrated with mybatis-plus without redis
    }
 
    public Object get(Object key, Function<Object, Object> function) {
        return cache.get(key, function);
    }

    public void remove(Object key) {
        cache.invalidate(key);
    }
 
    public void cleanUp(a) {
        cache.cleanUp();
    }
 
    public long estimatedSize(a) {
        returncache.estimatedSize(); }}Copy the code

MybatisCache

Mybatis- Plus local cache, the official website says that the recommendation is the Service layer, MY side is in the Dao layer cache.

/** * Secondary cache implementation *@authorHongMinFeng * /
@Slf4j
public class MybatisCache implements Cache {

    private CaffeineService caffeineService;

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
 
    private String id;
 
    public MybatisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }
 
    @Override
    public String getId(a) {
        return this.id;
    }
 
 
    @Override
    public void putObject(Object key, Object value) {

        if (caffeineService == null) {
            caffeineService = SpringUtil.getBean(CaffeineService.class);
        }
        log.info("Add to cache");
        if(value ! =null) { caffeineService.put(key, value); }}@Override
    public Object getObject(Object key) {
        if (caffeineService == null) {
            caffeineService = SpringUtil.getBean(CaffeineService.class);
        }
        log.info("Get cache"+key.toString());
        try {
            if(key ! =null) {
                returncaffeineService.getIfPresent(key); }}catch (Exception e) {
            log.error("Cache error");
        }
        return null;
    }
 
    ** * As of 3.3.0 this method is only called during a rollback * for any previous value that was missing in the cache  This lets any blocking cache to release the lock that * may have previously put on the key. * A blocking cache puts a lock when a value is null * and releases it when the value is back again. * This way other threads will wait for the value to be * available instead of hitting the database. * *@param key The key
     * @return Not used
     */
    @Override
    public Object removeObject(Object key) {
        if (caffeineService == null) {
            caffeineService = SpringUtil.getBean(CaffeineService.class);
        }
        log.info("Remove cache");

        caffeineService.remove(key);
        return null;
    }
 
    /** * Clears this cache instance. */
    @Override
    public void clear(a) {
        if (caffeineService == null) {
            caffeineService = SpringUtil.getBean(CaffeineService.class);
        }
        log.info("Clear cache");
        caffeineService.cleanUp();
    }
 
    /**
     * Optional. This method is not called by the core.
     *
     * @return The number of elements stored in the cache (not its capacity).
     */
    @Override
    public int getSize(a) {
        if (caffeineService == null) {
            caffeineService = SpringUtil.getBean(CaffeineService.class);
        }
        log.info("Get cache size");

        return (int) caffeineService.estimatedSize();
    }
 
    As of 3.2.6 this method is no longer called by the core. * <p> * Any locking needed by the cache must be  provided internally by the cache provider. * *@return A ReadWriteLock
     */
    @Override
    public ReadWriteLock getReadWriteLock(a) {
        return this.readWriteLock; }}Copy the code
@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtil.applicationContext = applicationContext;
    }

    public static Object getBean(String name){
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(String name, Class<T> clazz){
        return applicationContext.getBean(name, clazz);
    }

    public static <T> T getBean(Class<T> clazz){
        returnapplicationContext.getBean(clazz); }}Copy the code

Configure caching for the Mapper layer

@CacheNamespace(implementation= MybatisCache.class,eviction=MybatisCache.class)
public interface BsStreetMapper extends BaseMapper<BsStreet> {}Copy the code

Namespace in mapper XML file life

<mapper namespace="com.example.elsaddress.mapper.BsStreetMapper">
    <cache-ref namespace="com.example.elsaddress.mapper.BsStreetMapper"/>

</mapper>
Copy the code

rendering

Let’s write an example of our own to test it:

@Test
void readJsonFromTxt(a) throws FileNotFoundException {
    UserAddress byId = service.getById(930929442728595456L);
    UserAddress byId1 = service.getById(930929442728595456L);
    UserAddress byId2 = service.getById(930929502258352128L);
    QueryWrapper query = new QueryWrapper();
    query.eq("STREET_NAME"."Tung Wah Mun Street");
    BsStreet one = streetService.getOne(query);
    System.out.println(one);
}
Copy the code

First you get the same one twice, then you get another one, and then you try Street, and that’s about it.

As we can see, when he first fetched it, the cache didn’t exist, so he went to DB and found the data, and added it to the cache. The second fetch, found in the cache, directly back to the cache.

If we check it in Debug mode, it will be more obvious.

You can see that our results are cached in the Cache. There are two ways of caching, one is to cache SQL statements, such as this article, and another is to specify the Key, generally that kind of specific resource name, PV data, UV data, etc.

At the end

Follow-up to be completed

  • Locate the address based on the user’s latitude and longitude. Redis has Geo data structure, can deal with latitude and longitude data very well, feel can be used.

At this point, we have basically completed the logistics address service capability, the next chapter will be to complete the logistics service and logistics details. Please keep your eyes on me! Ball ball everyone 🧓 give me a thumbs-up, your thumbs-up is really very important!