Система всплесков высокой параллелизма в Java [резюме после просмотра]

Java Java EE

Описание Проекта

Я нашел проект JavaWeb на веб-сайте MOOC, содержание которого посвящено всплескам высокой параллелизма, я подумал, что это очень интересно, поэтому я зашел и изучил его.

Задокументируйте то, что вы узнали в этом проекте.

Адрес gitHub, соответствующий исходному коду проекта (написанный человеком, смотрящим его видео, а не исходным кодом видео):GitHub.com/coding X IA надеется…

Я собрал воедино то, что узнал из проекта, комбинируя его материалы и просматривая видео...

Уровень проекта Дао

Инструмент ведения журнала:



    <!--1.日志 java日志有:slf4j,log4j,logback,common-logging
        slf4j:是规范/接口
        日志实现:log4j,logback,common-logging
        使用:slf4j+logback
    -->

Свойства конфигурации, которые Mybatis раньше не замечал:

Используйте jdbc getGeneratekeys, чтобы получить самоувеличивающееся значение первичного ключа., это свойство весьма полезно.


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--配置全局属性-->
    <settings>
        <!--使用jdbc的getGeneratekeys获取自增主键值-->
        <setting name="useGeneratedKeys" value="true"/>
        <!--使用列别名替换列名&emsp;&emsp;默认值为true
        select name as title(实体中的属性名是title) form table;
        开启后mybatis会自动帮我们把表中name的值赋到对应实体的title属性中
        -->
        <setting name="useColumnLabel" value="true"/>

        <!--开启驼峰命名转换Table:create_time到 Entity(createTime)-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

</configuration>

Если объект, возвращаемый Mybatis, имеет ассоциированные поля, помимо использования resultMap, есть еще следующие методы (хотя я все же думаю, что resultMap будет удобнее)


    <select id="queryByIdWithSeckill" resultType="SuccessKilled">

        <!--根据seckillId查询SuccessKilled对象,并携带Seckill对象-->
        <!--如何告诉mybatis把结果映射到SuccessKill属性同时映射到Seckill属性-->
        <!--可以自由控制SQL语句-->
        SELECT
            sk.seckill_id,
            sk.user_phone,
            sk.create_time,
            sk.state,
            s.seckill_id "seckill.seckill_id",
            s.name "seckill.name",
            s.number "seckill",
            s.start_time "seckill.start_time",
            s.end_time "seckill.end_time",
            s.create_time "seckill.create_time"
        FROM success_killed sk
        INNER JOIN seckill s ON sk.seckill_id=s.seckill_id
        WHERE sk.seckill_id=#{seckillId}
        AND sk.user_phone=#{userPhone}
    </select>

Свойства, которые могут использоваться пулом соединений с базой данных:


        <!--c3p0私有属性-->
        <property name="maxPoolSize" value="30"/>
        <property name="minPoolSize" value="10"/>
        <!--关闭连接后不自动commit-->
        <property name="autoCommitOnClose" value="false"/>

        <!--获取连接超时时间-->
        <property name="checkoutTimeout" value="1000"/>
        <!--当获取连接失败重试次数-->
        <property name="acquireRetryAttempts" value="2"/>

Spring интегрируется с Junit:


/**
 * Created by codingBoy on 16/11/27.
 * 配置spring和junit整合,这样junit在启动时就会加载spring容器
 */
@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring的配置文件
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SeckillDaoTest {

    //注入Dao实现类依赖
    @Resource
    private SeckillDao seckillDao;


    @Test
    public void queryById() throws Exception {
        long seckillId=1000;
        Seckill seckill=seckillDao.queryById(seckillId);
        System.out.println(seckill.getName());
        System.out.println(seckill);
    }
}

Когда параметр Mybatis больше одного

Когда я раньше изучал MyBatis,Если имеется более одного параметра, он загружается с помощью коллекции Map!

Обнаруженный в этом руководстве, вы можете обойтись без коллекции карт (если все они являются базовыми типами данных)!

пример:Просто используйте @Param!


int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);

ПараметрType может быть проигнорирован непосредственно в файле XML!

Избегайте создания исключений при повторной вставке данных

Если первичный ключ неоднократно вставляется в данные, Mybatis обычно выдает исключение, а мы не хотим, чтобы он выдавал исключение, тогда мы можем сделать это:

Пишите в игнор..

Сервисный уровень

tdo

Пакет dto используется в качестве транспортного уровня.Разница между dto и сущностью заключается в следующем: сущность используется для инкапсуляции бизнес-данных,А dto используется для завершения передачи данных между веб-слоем и сервисным уровнем.

Что касается концепции dto, я уже однажды сталкивался с ней, но не применял ее на практике. На этот раз я видел его использование.

Мое понимание:Данные службы и веб-уровня переносят данные путем переупаковки объекта. Поскольку во многих случаях данные, возвращаемые уровнем службы, являются POJO, многие свойства POJO являются избыточными, и некоторые нужные данные не могут быть включены. В настоящее время,dto может еще раз абстрагировать данные для передачи и инкапсулировать данные для получения.

Определить несколько объектов исключений

Предыдущие объекты-исключения предназначены для всего бизнеса,На самом деле все же можно разделить несколько классов исключений.. Например, «повторное закрытие seckill» и «закрытие seckill» — это бизнес seckill.

Преимущество этого в том, что вы можете увидеть, что не так, увидев выброшенное исключение.

Для многих исключений, пойманных в сервисном слое на видео, я думаю, что их можно кинуть прямо в сервисный слой, а можно и в контроллер.Удобнее использовать унифицированный класс обработчика исключений, чтобы управлять им напрямую!

Продвигайте использование аннотаций для использования транзакций

Я думаю, что код более понятен, если использовать аннотации.

Под видео есть студенты, которые сказали, что если метод транзакции вызывается в Сервисе, то будут какие-то подводные камни, и я с этим пока не сталкивался. Сначала сохраните:

Параллелизм не увеличивается, потому что, когда несколько потоков одновременно обращаются к строке данных, генерируется транзакция, поэтому создается блокировка записи.Каждый раз, когда поток, который получает транзакцию, снимает блокировку, другой поток в очереди может получить блокировку записи. Количество запросов в секунду и время выполнения транзакции тесно связаны: чем короче время выполнения транзакции, тем выше уровень параллелизма, что также является причиной вынесения трудоемких операций ввода-вывода из транзакции.

При вызове методов транзакций в одном и том же классе есть ловушка.Студенты должны обратить внимание, что АОП не может вызывать методы транзакций. Сделка не вступит в силу.Решений несколько.Вы можете поискать и найти подходящее вам решение. Основная проблема заключается в том, что АОП не будет использовать прокси-сервер для вызова внутреннего метода, когда класс вызывается внутри.

Решение «При вызове методов транзакций в одном классе возникает ямка» 1. Если он основан на динамическом прокси-сервере интерфейса, нет проблем, просто используйте вызов интерфейса напрямую. 2. Если это динамический прокси на основе классов, это можно решить с помощью AopContext.currentProxy().Обратите внимание, что метод удаления должен быть общедоступным! !

Открытый интерфейс MD5

На самом деле, мне также интересно, действительно ли полезен URL-адрес, предоставленный MD5, и я видел, как люди задают вопросы.

Woohoo. IMO OC.com/QA Detail/16…

Ответил:

Нельзя сказать, что он не работает, если его не зашифровать, то пользователь перехватывает ваш адрес доступа, и он видит, что текущий seckill ID равен 1000. Он может полностью вывести другие адреса seckill, а может создать партию адресов секкилл в видео стоит в базе время секкилл определяется в базе естественно секкилл в другое время он не может но это тоже оказывает определенное влияние на базу если он использует таймер или циклическое программное обеспечение seckill, выносливость вашей системы является проблемой; с другой стороны, для некоторых людей, которые не начали второе убийство, после того, как он смоделирует адрес, он может использовать таймер для доступа к нему все время. После шифрования, поскольку он не может получить запутанный код, он может только щелкнуть ссылку, чтобы убить...

Простое понимание: после шифрования MD5 пользователь не может имитировать реальный адрес до всплеска, который все еще имеет определенный эффект.

перечисляемый класс

В ответ new SeckillExecution(seckillId, 1, "Seckill success", successKilled); в коде ** информация о параметрах состояния и stateInfo, которую мы возвращаем, должна выводиться во внешний интерфейс, но мы не хотим жестко кодировать эти два в наших параметрах кода возврата, поэтому мы должны рассмотреть возможность инкапсуляции этих констант с помощью перечисления**,



public enum SeckillStatEnum {

    SUCCESS(1,"秒杀成功"),
    END(0,"秒杀结束"),
    REPEAT_KILL(-1,"重复秒杀"),
    INNER_ERROR(-2,"系统异常"),
    DATE_REWRITE(-3,"数据篡改");

    private int state;
    private String info;

    SeckillStatEnum(int state, String info) {
        this.state = state;
        this.info = info;
    }

    public int getState() {
        return state;
    }


    public String getInfo() {
        return info;
    }


    public static SeckillStatEnum stateOf(int index)
    {
        for (SeckillStatEnum state : values())
        {
            if (state.getState()==index)
            {
                return state;
            }
        }
        return null;
    }
}

Навыки разработки веб-уровня

Обучение спокойному дизайну интерфейса

Я уже знакомился с идеей RESTful, но в первом проекте она не использовалась. Поскольку я еще не привык к этому, я боюсь писать невзрачный RESTful-интерфейс, поэтому планирую применить RESTful во втором проекте.

Справочная запись в блоге:Блог horror.cn на.com/afraid/512047…

Детали, неизвестные до SpringMVC

Аннотация @DateTimeFormat форматирует время! (это я еще не пробовал)


    <!--配置spring mvc-->
    <!--1,开启springmvc注解模式
    a.自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter
    b.默认提供一系列的功能:数据绑定,数字和日期的format@NumberFormat,@DateTimeFormat
    c:xml,json的默认读写支持-->
    <mvc:annotation-driven/>

    <!--2.静态资源默认servlet配置-->
    <!--
        1).加入对静态资源处理:js,gif,png
        2).允许使用 "/" 做整体映射
    -->
    <mvc:default-servlet-handler/>

Возвращает единообразно отформатированный JSON

Ранее dto был инкапсулирован в веб-уровень и сервис для передачи данных этих двух слоев, и мы обычно возвращаем JSON на внешний интерфейс для разбора в контроллере.

Лучше всего также унифицировать формат JSON. Это будет хорошим способом сформировать спецификацию!


//将所有的ajax请求返回类型,全部封装成json数据
public class SeckillResult<T> {

    private boolean success;
    private T data;
    private String error;

    public SeckillResult(boolean success, T data) {
        this.success = success;
        this.data = data;
    }

    public SeckillResult(boolean success, String error) {
        this.success = success;
        this.error = error;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }
}

Как получить данные JSON

Прежде чем получить JSON, я использовалobject.propertiesСпособ получить его, на этот раз я увидел другой путь:

Модульность JavaScript

Прежде чем писать JS-код в проекте, какую функцию вы хотели и где вы ее прописали.После просмотра этого видео я обнаружил, что JS может быть модульным! ! !

Читабельность модуляризации JS по-прежнему лучше, чем раньше.Это то, чего я раньше не касался, поэтому в будущем мне стоит обратить внимание на написание JS-кода!

Вставьте фрагмент кода ниже, чтобы почувствовать его:


/**
 *  模块化javaScript
 * Created by jianrongsun on 17-5-25.
 */
var seckill = {
    // 封装秒杀相关的ajax的url
    URL: {
        now: function () {
            return "/seckill/time/now";
        },
        exposer: function (seckillId) {
            return "/seckill/" + seckillId + "/exposer";
        },
        execution: function (seckillId, md5) {
            return "/seckill/" + seckillId + "/" + md5 + "/execution";
        }
    },
    // 验证手机号码
    validatePhone: function (phone) {
        return !!(phone && phone.length === 11 && !isNaN(phone));
    },
    // 详情页秒杀业务逻辑
    detail: {
        // 详情页开始初始化
        init: function (params) {
            console.log("获取手机号码");
            // 手机号验证登录,计时交互
            var userPhone = $.cookie('userPhone');
            // 验证手机号
            if (!seckill.validatePhone(userPhone)) {
                console.log("未填写手机号码");
                // 验证手机控制输出
                var killPhoneModal = $("#killPhoneModal");
                killPhoneModal.modal({
                    show: true,  // 显示弹出层
                    backdrop: 'static',  // 静止位置关闭
                    keyboard: false    // 关闭键盘事件
                });

                $("#killPhoneBtn").click(function () {
                    console.log("提交手机号码按钮被点击");
                    var inputPhone = $("#killPhoneKey").val();
                    console.log("inputPhone" + inputPhone);
                    if (seckill.validatePhone(inputPhone)) {
                        // 把电话写入cookie
                        $.cookie('userPhone', inputPhone, {expires: 7, path: '/seckill'});
                        // 验证通过 刷新页面
                        window.location.reload();
                    } else {
                        // todo 错误文案信息写到前端
                        $("#killPhoneMessage").hide().html("<label class='label label-danger'>手机号码错误</label>").show(300);
                    }
                });
            } else {
                console.log("在cookie中找到了电话号码,开启计时");
                // 已经登录了就开始计时交互
                var startTime = params['startTime'];
                var endTime = params['endTime'];
                var seckillId = params['seckillId'];
                console.log("开始秒杀时间=======" + startTime);
                console.log("结束秒杀时间========" + endTime);
                $.get(seckill.URL.now(), {}, function (result) {
                    if (result && result['success']) {
                        var nowTime = seckill.convertTime(result['data']);
                        console.log("服务器当前的时间==========" + nowTime);
                        // 进行秒杀商品的时间判断,然后计时交互
                        seckill.countDown(seckillId, nowTime, startTime, endTime);
                    } else {
                        console.log('结果:' + result);
                        console.log('result' + result);
                    }
                });
            }

        }
    },
    handlerSeckill: function (seckillId, mode) {
        // 获取秒杀地址
        mode.hide().html('<button class="btn btn-primary btn-lg" id="killBtn">开始秒杀</button>');
        console.debug("开始进行秒杀地址获取");
        $.get(seckill.URL.exposer(seckillId), {}, function (result) {
            if (result && result['success']) {
                var exposer = result['data'];
                if (exposer['exposed']) {
                    console.log("有秒杀地址接口");
                    // 开启秒杀,获取秒杀地址
                    var md5 = exposer['md5'];
                    var killUrl = seckill.URL.execution(seckillId, md5);
                    console.log("秒杀的地址为:" + killUrl);
                    // 绑定一次点击事件
                    $("#killBtn").one('click', function () {
                        console.log("开始进行秒杀,按钮被禁用");
                        // 执行秒杀请求,先禁用按钮
                        $(this).addClass("disabled");
                        // 发送秒杀请求
                        $.post(killUrl, {}, function (result) {
                            var killResult = result['data'];
                            var state = killResult['state'];
                            var stateInfo = killResult['stateInfo'];
                            console.log("秒杀状态" + stateInfo);
                            // 显示秒杀结果
                            mode.html('<span class="label label-success">' + stateInfo + '</span>');

                        });

                    });
                    mode.show();
                } else {
                    console.warn("还没有暴露秒杀地址接口,无法进行秒杀");
                    // 未开启秒杀
                    var now = seckill.convertTime(exposer['now']);
                    var start = seckill.convertTime(exposer['start']);
                    var end = seckill.convertTime(exposer['end']);
                    console.log("当前时间" + now);
                    console.log("开始时间" + start);
                    console.log("结束时间" + end);
                    console.log("开始倒计时");
                    console.debug("开始进行倒计时");
                    seckill.countDown(seckillId, now, start, end);
                }
            } else {
                console.error("服务器端查询秒杀商品详情失败");
                console.log('result' + result.valueOf());
            }
        });
    },
    countDown: function (seckillId, nowTime, startTime, endTime) {
        console.log("秒杀的商品ID:" + seckillId + ",服务器当前时间:" + nowTime + ",开始秒杀的时间:" + startTime + ",结束秒杀的时间" + endTime);
        //  获取显示倒计时的文本域
        var seckillBox = $("#seckill-box");
        //  获取时间戳进行时间的比较
        nowTime = new Date(nowTime).valueOf();
        startTime = new Date(startTime).valueOf();
        endTime = new Date(endTime).valueOf();
        console.log("转换后的Date类型当前时间戳" + nowTime);
        console.log("转换后的Date类型开始时间戳" + startTime);
        console.log("转换后的Date类型结束时间戳" + endTime);
        if (nowTime < endTime && nowTime > startTime) {
            // 秒杀开始
            console.log("秒杀可以开始,两个条件符合");
            seckill.handlerSeckill(seckillId, seckillBox);
        }
        else if (nowTime > endTime) {
            alert(nowTime > endTime);
            // console.log(nowTime + ">" + startTime);
            console.log(nowTime + ">" + endTime);

            // 秒杀结束
            console.warn("秒杀已经结束了,当前时间为:" + nowTime + ",秒杀结束时间为" + endTime);
            seckillBox.html("秒杀结束");
        } else {
            console.log("秒杀还没开始");
            alert(nowTime < startTime);
            // 秒杀未开启
            var killTime = new Date(startTime + 1000);
            console.log(killTime);
            console.log("开始计时效果");
            seckillBox.countdown(killTime, function (event) {
                // 事件格式
                var format = event.strftime("秒杀倒计时: %D天 %H时 %M分 %S秒");
                console.log(format);
                seckillBox.html(format);
            }).on('finish.countdown', function () {
                // 事件完成后回调事件,获取秒杀地址,控制业务逻辑
                console.log("准备执行回调,获取秒杀地址,执行秒杀");
                console.log("倒计时结束");
                seckill.handlerSeckill(seckillId, seckillBox);
            });
        }
    },
    cloneZero: function (time) {
        var cloneZero = ":00";
        if (time.length < 6) {
            console.warn("需要拼接时间");
            time = time + cloneZero;
            return time;
        } else {
            console.log("时间是完整的");
            return time;
        }
    },
    convertTime: function (localDateTime) {
        var year = localDateTime.year;
        var monthValue = localDateTime.monthValue;
        var dayOfMonth = localDateTime.dayOfMonth;
        var hour = localDateTime.hour;
        var minute = localDateTime.minute;
        var second = localDateTime.second;
        return year + "-" + monthValue + "-" + dayOfMonth + " " + hour + ":" + minute + ":" + second;
    }
};

Высокая оптимизация производительности параллелизма

Первые три статьи уже завершили эту систему, но как система seckill количество параллелизма, которое она может поддерживать, очень невелико. Так что теперь нам нужно подумать, как его настроить.

анализировать

Адресный интерфейс шипа можно оптимизировать с помощью Redis, без многократного доступа к базе данных.

Операция seckill связана с транзакцией базы данных и не может быть заменена кешем. Приведенное ниже решение требует изменения исходного кода, и сложность его относительно сложна.

Давайте проанализируем, где узкое место:

  • Mysql выполняет один оператор SQL на самом деле очень быстро.
  • В основном ожидание транзакций блокировки на уровне строк, сетевых задержек и перезапуска GC!

Решения:

Решить интерфейс seckill

Для интерфейса seckill вам нужно использовать Redis для кэширования данных. Тогда пользователю не нужно обращаться к базе данных при доступе, мы просто кэшируем данные для Redis.

На этот раз используйте Jedis для управления Redis.

Также стоит отметить:Мы можем использовать ProtostuffIOUtil для замены сериализации JDK, потому что эта функция сериализации намного лучше, чем в JDK!


package com.suny.dao.cache;

import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import com.suny.entity.Seckill;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * 操作Redis的dao类
 * Created by 孙建荣 on 17-5-27.下午4:44
 */
public class RedisDao {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final JedisPool jedisPool;

    private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);

    public RedisDao(String ip, int port) {
        jedisPool = new JedisPool(ip, port);
    }

    public Seckill getSeckill(long seckillId) {
        // redis操作业务逻辑
        try (Jedis jedis = jedisPool.getResource()) {
            String key = "seckill:" + seckillId;
            // 并没有实现内部序列化操作
            //get->byte[]字节数组->反序列化>Object(Seckill)
            // 采用自定义的方式序列化
            // 缓存获取到
            byte[] bytes = jedis.get(key.getBytes());
            if (bytes != null) {
                // 空对象
                Seckill seckill = schema.newMessage();
                ProtostuffIOUtil.mergeFrom(bytes, seckill, schema);
                // seckill被反序列化
                return seckill;
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public String putSeckill(Seckill seckill) {
        //  set Object(Seckill) -> 序列化 -> byte[]
        try (Jedis jedis = jedisPool.getResource()) {
            String key = "seckill:" + seckill.getSeckillId();
            byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema,
                    LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
            // 超时缓存
            int timeout=60*60;
            return jedis.setex(key.getBytes(), timeout, bytes);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }


}



        <!--导入连接redis的JAR包-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

        <!--添加序列化依赖-->
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.1.1</version>
        </dependency>

RedisDao не зависит от прокси Mybatis, поэтому нам нужно создать его вручную.

В конечном итоге логика нашего сервиса будет выглядеть так:

Оптимизация работы Seckill

Возвращаясь снова к нашей операции seckill, на самом деле нужно оптимизировать время ожидания наших блокировок GC и строк.

Наша предыдущая логика была следующей:Сначала выполните операцию сокращения запасов, а затем вставьте запись об успешной покупке.

На самом деле мы можем сначала вставить запись об успешной покупке, а потом уже выполнять операцию уменьшения запасов!

  • Так в чем разница между ними? ? ?Операция сокращения инвентаря приведет к ожиданию блокировок на уровне строк, и если мы вставим их первыми, блокировки на уровне строк не будут мешать нам. Более того, наши две операции в одном и том же, и никакой ситуации «перепроданности» не будет!

Что касается разницы между выполнением сначала вставки и выполнением сначала обновления,Когда две транзакции вставляются одновременно, нет конкуренции за блокировку, и скорость выполнения будет выше.Когда две транзакции сначала обновляют одну и ту же строку данных, одна транзакция получит блокировку строки, и блокировка не будет снята до тех пор, пока транзакция зафиксирована, поэтому пусть блокировка будет Кратчайшее время удержания может повысить эффективность

Таким образом, логику нашего сервисного слоя можно изменить следующим образом:

Это не окончательное решение.Для оптимизации производительности мы также можем запускать SQL в Mysql без управления транзакциями Spring.Использование хранимых процедур в Mysql для представления производительности



-- 秒杀执行储存过程
DELIMITER ? -- console ; 转换为
?
-- 定义储存过程
-- 参数: in 参数   out输出参数
-- row_count() 返回上一条修改类型sql(delete,insert,update)的影响行数
-- row_count:0:未修改数据 ; >0:表示修改的行数; <0:sql错误
CREATE PROCEDURE `seckill`.`execute_seckill`
  (IN v_seckill_id BIGINT, IN v_phone BIGINT,
   IN v_kill_time  TIMESTAMP, OUT r_result INT)
  BEGIN
    DECLARE insert_count INT DEFAULT 0;
    START TRANSACTION;
    INSERT IGNORE INTO success_killed
    (seckill_id, user_phone, create_time)
    VALUES (v_seckill_id, v_phone, v_kill_time);
    SELECT row_count()
    INTO insert_count;
    IF (insert_count = 0)
    THEN
      ROLLBACK;
      SET r_result = -1;
    ELSEIF (insert_count < 0)
      THEN
        ROLLBACK;
        SET r_result = -2;
    ELSE
      UPDATE seckill
      SET number = number - 1
      WHERE seckill_id = v_seckill_id
            AND end_time > v_kill_time
            AND start_time < v_kill_time
            AND number > 0;
      SELECT row_count()
      INTO insert_count;
      IF (insert_count = 0)
      THEN
        ROLLBACK;
        SET r_result = 0;
      ELSEIF (insert_count < 0)
        THEN
          ROLLBACK;
          SET r_result = -2;
      ELSE
        COMMIT;
        SET r_result = 1;

      END IF;
    END IF;
  END;
?
--  储存过程定义结束
DELIMITER ;
SET @r_result = -3;
--  执行储存过程
CALL execute_seckill(1003, 13502178891, now(), @r_result);
-- 获取结果
SELECT @r_result;

Mybatis, вызывающий хранимые процедуры, на самом деле такой же, как JDBC:

При использовании хранимой процедуры нам нужно 4 параметра, по сути результат присваивается в хранимой процедуре.Мы можем получить соответствующее значение через MapUtils. Это то, к чему я раньше не прикасался.

Наконец, системная архитектура для развертывания должна выглядеть так:

Суммировать

Потратив некоторое время на просмотр этого видеоурока, я чувствую, что многому научился. Раньше я не сталкивался с проблемами, связанными с оптимизацией, но теперь это открыло мне глаза и я узнал много спецификаций разработки, что тоже очень хорошо. Если вы новичок, вы можете научиться этому.

Если в статье есть какие-либо ошибки, пожалуйста, поправьте меня, и мы сможем общаться друг с другом. Учащиеся, привыкшие читать технические статьи в WeChat и желающие получить больше ресурсов по Java, могутОбратите внимание на публичный аккаунт WeChat: Java3y