ssm环境搭建和红包超发问题

逆向工程生成mapper.java、mapper.xml、po

使用从逆向工程说明处的模板项目

GeneratorSqlmap.java



import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.DefaultShellCallback;

public class GeneratorSqlmap {

    public void generator() throws Exception{

        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        //指定 逆向工程配置文件
        File configFile = new File("generatorConfig.xml"); 
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
                callback, warnings);
        myBatisGenerator.generate(null);

    } 
    public static void main(String[] args) throws Exception {
        try {
            GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
            generatorSqlmap.generator();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }

}

generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="testTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>
        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/red_packet" userId="root"
            password="suntong">
        </jdbcConnection>
        <!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
            connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg" 
            userId="yycg"
            password="yycg">
        </jdbcConnection> -->

        <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和 
            NUMERIC 类型解析为java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- targetProject:生成PO类的位置 -->
        <javaModelGenerator targetPackage="com.redpacket.ssm.po"
            targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        
        <!-- targetProject:mapper映射文件生成的位置 -->
        <sqlMapGenerator targetPackage="com.redpacket.ssm.mapper" 
            targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>
        
        <!-- targetPackage:mapper接口生成的位置 -->
        <javaClientGenerator type="XMLMAPPER"
            targetPackage="com.redpacket.ssm.mapper" 
            targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>
        
        <!-- 指定数据库表 -->
        <table tableName="t_red_packet"></table>
        <table tableName="t_user_red_packet"></table>
        <!-- <table schema="" tableName="sys_user"></table>
        <table schema="" tableName="sys_role"></table>
        <table schema="" tableName="sys_permission"></table>
        <table schema="" tableName="sys_user_role"></table>
        <table schema="" tableName="sys_role_permission"></table> -->
        
        <!-- 有些表的字段需要指定java类型
         <table schema="" tableName="">
            <columnOverride column="" javaType="" />
        </table> -->
    </context>
</generatorConfiguration>

运行java代码,将生成的po、mapper拷贝到主项目中,不建议在生成的代码上做操作,需要修改需求可以对生成的po、mapper进行包装。

关于po是否要实现序列化接口

序列化po主要为了实现二级缓存、分布式缓存,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存。

参考:

https://www.jianshu.com/writer#/notebooks/39849728/notes/53800305/preview

在我们的抢红包项目中对实时性要求极高,所以不采用二级缓存。所以就不考虑序列化pojo类了。

搭建ssm框架(Dao层):

新建ssm_redpacket项目,导入jar包

使用的jdk为1.8
经过自己测试导入spring4.2.4的jar包正好好使,低版本运行时会有很多找不到对应包的错误。

spring整合mybatis sqlMapConfig

新建源文件夹config-包mybatis

sqlMapConfig.xml

<?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>

        <!-- 全局setting配置
            根据需要加
         -->
         

        <!-- 配置别名 -->
        <typeAliases>
            <!-- 批量扫描别名 -->
            <package name="com.redpacket.ssm.po"/>
        </typeAliases>
        

        <!-- 配置mapper  
            由于使用spring和mybatis的整合包进行mapper扫描,这里不需要配置 
            必须遵循:mapper.xml和mapper.java文件同名,且在一个目录
        -->
        
</configuration>

(没剩多少内容了,主要部分sqlSessionFactory、mapper扫描都由spring接管)

applicationContext-dao.xml

<?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>

        <!-- 全局setting配置
            根据需要加
         -->
         

        <!-- 配置别名 -->
        <typeAliases>
            <!-- 批量扫描别名 -->
            <package name="com.redpacket.ssm.po"/>
        </typeAliases>
        

        <!-- 配置mapper  
            由于使用spring和mybatis的整合包进行mapper扫描,这里不需要配置 
            必须遵循:mapper.xml和mapper.java文件同名,且在一个目录
        -->
        
</configuration>

db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/red_packet
jdbc.username=root
jdbc.password=suntong



log4j.properties

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

在TRedPacketMapper.java中添加两个方法 , 一个是查询红包,另一个是扣减红包库存。
抢红包的逻辑是,先查询红包的信息,看其是否拥有存量可以扣减。如果有存量,那么可以扣减它,否则就不扣减。

package com.redpacket.ssm.mapper;

import com.redpacket.ssm.po.TRedPacket;
import com.redpacket.ssm.po.TRedPacketExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;

public interface TRedPacketMapper {
    int countByExample(TRedPacketExample example);

    int deleteByExample(TRedPacketExample example);

    int deleteByPrimaryKey(Integer id);

    int insert(TRedPacket record);

    int insertSelective(TRedPacket record);

    List<TRedPacket> selectByExample(TRedPacketExample example);

    //查询红包具体信息已包含
    TRedPacket selectByPrimaryKey(Integer id);

    int updateByExampleSelective(@Param("record") TRedPacket record, @Param("example") TRedPacketExample example);

    int updateByExample(@Param("record") TRedPacket record, @Param("example") TRedPacketExample example);

    int updateByPrimaryKeySelective(TRedPacket record);

    int updateByPrimaryKey(TRedPacket record);
    
    //新增按照id扣减红包库存
    int decreaseRedPacket(Integer id);
}

对应Mapper映射文件

<!-- 扣减抢红包库存 -->
    <update id="decreaseRedPacket">
        update t_red_packet set stock = stock - 1 where id =
        #{id,jdbcType=INTEGER}
    </update>

(映射太多了,只摘咱们添加的)

TUserRedPacketMapper.java

package com.redpacket.ssm.mapper;

import com.redpacket.ssm.po.TUserRedPacket;
import com.redpacket.ssm.po.TUserRedPacketExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;

public interface TUserRedPacketMapper {

    
    /**
     * 插入抢红包信息.
     * @param userRedPacket ——抢红包信息
     * @return 影响记录数.
     */
    public int grapRedPacket(TUserRedPacket  tUserRedPacket);
}

对应mapper映射文件

<!-- 插入抢红包信息 -->
    <insert id="grapRedPacket" useGeneratedKeys="true" 
    keyProperty="id" parameterType="com.redpacket.ssm.po.TUserRedPacket">
        insert into T_USER_RED_PACKET( red_packet_id, user_id, amount, grab_time, note)
        values (#{redPacketId}, #{userId}, #{amount}, now(), #{note}) 
    </insert>

这里使用了 useGeneratedKeys 和 keyProperty,这就意味着会Mybatis执行完插入语句后,自动将自增长值赋值给对象的属性id。这样就可以拿到插入记录的主键了 , 关于 DAO 层就基本完成了。

web.xml

<!-- 加载spring容器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/classes/spring/applicationContext-*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

新建两个mapper的测试类测试对应的方法

TRedPacketMapperTest.java

package com.redpacket.ssm.test;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.redpacket.ssm.mapper.TRedPacketMapper;
import com.redpacket.ssm.po.TRedPacket;

public class TRedPacketMapperTest {
    
    private ApplicationContext applicationContext;

    //再setUp中构造spring容器
    @Before
    public void setUp() throws Exception{
        applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-dao.xml");
    }

    @Test
    public void testSelectByPrimaryKey() {
        
        TRedPacketMapper tRedPacketMapper = (TRedPacketMapper)applicationContext.getBean("TRedPacketMapper");
        
        TRedPacket tRedPacket = tRedPacketMapper.selectByPrimaryKey(1);
        
        System.out.println(tRedPacket);
    }

    @Test
    public void testDecreaseRedPacket() {
        TRedPacketMapper tRedPacketMapper = (TRedPacketMapper)applicationContext.getBean("TRedPacketMapper");
        
        tRedPacketMapper.decreaseRedPacket(1);
        
    }

}

TUserRedPacketMapperTest.java

package com.redpacket.ssm.test;

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.util.Date;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.redpacket.ssm.mapper.TRedPacketMapper;
import com.redpacket.ssm.mapper.TUserRedPacketMapper;
import com.redpacket.ssm.po.TRedPacket;
import com.redpacket.ssm.po.TUserRedPacket;

public class TUserRedPacketMapperTest {
    
    private ApplicationContext applicationContext;

    //再setUp中构造spring容器
    @Before
    public void setUp() throws Exception{
        applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-dao.xml");
    }

    @Test
    public void testGrapRedPacket() {
        

        TUserRedPacketMapper tUserRedPacketMapper = (TUserRedPacketMapper)applicationContext.getBean("TUserRedPacketMapper");
        
        TUserRedPacket tUserRedPacket = new TUserRedPacket();
        
        tUserRedPacket.setAmount(new BigDecimal(10.0));
        tUserRedPacket.setGrabTime(new Date());
        tUserRedPacket.setRedPacketId(1);
        tUserRedPacket.setUserId(1);
        
        tUserRedPacketMapper.grapRedPacket(tUserRedPacket);
        
        
    }

}

查询后输出的结果是一个对象地址,如果想增加toString方法最好新建一个包装类,不要在原pojo上做改动,增强以后的拓展性。

增添和修改到数据库总看方便(此处忘记截图)

工程结构

搭建ssm框架(service层)

考虑到项目主要功能是测试高并发情况下如何保证数据安全,所以不用注解了,xml配置更加稳定容易,不易出现意料之外的问题(半注解半xml配置的话要在xml中开启注解配置,全注解的话要继承配置类,全注解并不爽,

<!-- 指定扫描com.fei.bean包下的所有类中的注解  -->
    <context:component-scan base-package="com.fei.bean"></context:component-scan>

半xml加注解时在xml中配置这句话,spring就能开始扫描包下所有的注解)

RedPacketService.java

package com.redpacket.ssm.service;

import com.redpacket.ssm.po.TRedPacket;

public interface RedPacketService {

    /**
     * 获取红包
     * @param id——编号
     * @return 红包信息
     */
    public TRedPacket getRedPacket(Integer id);

    /**
     * 扣减红包
     * @param id——编号
     * @return 影响条数.
     */
    public int decreaseRedPacket(Integer id);
}

UserRedPacketService.java

package com.redpacket.ssm.service;

public interface UserRedPacketService {

    /**
     * 保存抢红包信息.
     * @param redPacketId 红包编号
     * @param userId 抢红包用户编号
     * @return 影响记录数.
     */
    public int grapRedPacket(Integer redPacketId, Integer userId);

}

RedPacketServiceImpl.java

package com.redpacket.ssm.service.impl;

import org.springframework.beans.factory.annotation.Autowired;

import com.redpacket.ssm.mapper.TRedPacketMapper;
import com.redpacket.ssm.po.TRedPacket;
import com.redpacket.ssm.service.RedPacketService;

public class RedPacketServiceImpl implements RedPacketService {

    @Autowired
    private TRedPacketMapper tRedPacketMapper;

    @Override
    public TRedPacket getRedPacket(Integer id) {
        return tRedPacketMapper.selectByPrimaryKey(id);
    }

    @Override
    public int decreaseRedPacket(Integer id) {
        return tRedPacketMapper.decreaseRedPacket(id);
    }


}

UserRedPacketServiceImpl.java

package com.redpacket.ssm.service.impl;

import java.math.BigDecimal;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.redpacket.ssm.mapper.TRedPacketMapper;
import com.redpacket.ssm.mapper.TUserRedPacketMapper;
import com.redpacket.ssm.po.TRedPacket;
import com.redpacket.ssm.po.TUserRedPacket;
import com.redpacket.ssm.service.UserRedPacketService;

public class UserRedPacketServiceImpl implements UserRedPacketService {
        @Autowired
        private TRedPacketMapper tRedPacketMapper;

        @Autowired
        private TUserRedPacketMapper tUserRedPacketMapper;

        // 失败
        final int FAILED = 0;

        @Override
        public int grapRedPacket(Integer redPacketId, Integer userId) {
            // 获取红包信息
            TRedPacket tRedPacket = tRedPacketMapper.selectByPrimaryKey(redPacketId);
            int leftRedPacket = tRedPacket.getStock();
            // 当前小红包库存大于0
            if (leftRedPacket > 0) {
                tRedPacketMapper.decreaseRedPacket(redPacketId);
                // logger.info("剩余Stock数量:{}", leftRedPacket);
                // 生成抢红包信息
                TUserRedPacket tUserRedPacket = new TUserRedPacket();
                tUserRedPacket.setRedPacketId(redPacketId);
                tUserRedPacket.setUserId(userId);
                tUserRedPacket.setAmount(new BigDecimal(tRedPacket.getUnitAmount()));
                tUserRedPacket.setNote("redpacket- " + redPacketId);
                // 插入抢红包信息
                int result = tUserRedPacketMapper.grapRedPacket(tUserRedPacket);
                return result;
            }
            // logger.info("没有红包啦.....剩余Stock数量:{}", leftRedPacket);
            // 失败返回
            return FAILED;
        }

}

applicationContext-service.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.2.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 商品管理的service -->
<bean id="redPacketService" class="com.redpacket.ssm.service.impl.RedPacketServiceImpl"/>
<bean id="userRedPacketService" class="com.redpacket.ssm.service.impl.UserRedPacketServiceImpl"/>
</beans>

applicationContext-transaction.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.2.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

<!-- 事务管理器 
    对mybatis操作数据库事务控制,spring使用jdbc的事务控制类
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 数据源
    dataSource在applicationContext-dao.xml中配置了
     -->
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 传播行为 -->
        <tx:method name="save*" propagation="REQUIRED"/>
        <tx:method name="delete*" propagation="REQUIRED"/>
        <tx:method name="insert*" propagation="REQUIRED"/>
        <tx:method name="grap*" propagation="REQUIRED"/>
        <tx:method name="update*" propagation="REQUIRED"/>
        <tx:method name="decrease*" propagation="REQUIRED"/>
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
        <tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>

<!-- aop -->
<aop:config>
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.itcast.ssm.service.impl.*.*(..))"/>
</aop:config>

</beans>

事务配置参考

https://www.jianshu.com/writer#/notebooks/39651087/notes/53434246/preview

单元测试

RedPacketServiceTest.java

package com.redpacket.ssm.test;


import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.redpacket.ssm.po.TRedPacket;
import com.redpacket.ssm.service.impl.RedPacketServiceImpl;

public class RedPacketServiceTest {
    
    private ApplicationContext applicationContext;

    @Before
    public void setUp() throws Exception{
        applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-service.xml","classpath:spring/applicationContext-dao.xml");
    }

    
    @Test
    public void testGetRedPacket() {
        
        RedPacketServiceImpl redPacketServiceImpl = (RedPacketServiceImpl)applicationContext.getBean("redPacketService");
        
        TRedPacket tRedPacket = redPacketServiceImpl.getRedPacket(1);
        
        System.out.println(tRedPacket);
    }

    @Test
    public void testDecreaseRedPacket() {
        RedPacketServiceImpl redPacketServiceImpl = (RedPacketServiceImpl)applicationContext.getBean("redPacketService");
        
        int a = redPacketServiceImpl.decreaseRedPacket(1);
        
        System.out.println(a);
    }

}

测试方法testDecreaseRedPacket()

测试方法testGetRedPacket()

UserRedPacketServiceImplTest.java

package com.redpacket.ssm.test;

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.util.Date;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.redpacket.ssm.service.impl.UserRedPacketServiceImpl;

public class UserRedPacketServiceImplTest {

    private ApplicationContext applicationContext;

    @Before
    public void setUp() throws Exception{
        applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-service.xml","classpath:spring/applicationContext-dao.xml");
    }

    
    
    @Test
    public void testGrapRedPacket() {
        UserRedPacketServiceImpl userRedPacketServiceImpl = (UserRedPacketServiceImpl)applicationContext.getBean("userRedPacketService");
        
        int a = userRedPacketServiceImpl.grapRedPacket(1, 2);
        System.out.println(a);
    }

}

绿了绿了!!!绿了就好使了

搭建ssm框架(Controller层):

UserRedPacketController.java

package com.redpacket.ssm.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.redpacket.ssm.service.UserRedPacketService;

@Controller
@RequestMapping("/userRedPacket")
public class UserRedPacketController {

    @Autowired
    private UserRedPacketService userRedPacketService;
    
    @RequestMapping("/grapRedPacket")
    public @ResponseBody Map<String, Object> grapRedPacket(Integer redPacketId, Integer userId) {
        // 抢红包
        int result = userRedPacketService.grapRedPacket(redPacketId, userId);
        Map<String, Object> retMap = new HashMap<String, Object>();
        boolean flag = result > 0;
        retMap.put("success", flag);
        retMap.put("message", flag ? "抢红包成功" : "抢红包失败");
        return retMap;
    }   

}

DateConverter.java

package com.redpacket.ssm.controller.converter;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.core.convert.converter.Converter;

public class DateConverter implements Converter<String, Date> {

    @Override
    public Date convert(String source) {
        
        //实现日期串转成日期类型(格式"yyyy-MM-dd HH:mm:ss")
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //转成直接返回
            return simpleDateFormat.parse(source);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //如果参数绑定失败返回null
        return null;
    }

}

实现日期串转换成日期类型

springmvc.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.2.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <!-- 可以扫描controller、service、...
    这里让扫描controller,指定controller的包
     -->
    <context:component-scan base-package="com.redpacket.ssm.controller"></context:component-scan>
    
    
    <!-- 使用 mvc:annotation-driven代替上边注解映射器和注解适配器配置
    mvc:annotation-driven默认加载很多的参数绑定方法,
    比如json转换解析器就默认加载了,如果使用mvc:annotation-driven不用配置上边的RequestMappingHandlerMapping和RequestMappingHandlerAdapter
    实际开发时使用mvc:annotation-driven
     -->
    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
    

    <!-- 自定义参数绑定 -->
    <bean id="conversionService"
        class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <!-- 转换器 -->
        <property name="converters">
            <list>
                <!-- 日期类型转换 -->
                <bean class="com.redpacket.ssm.controller.converter.DateConverter"/>
            </list>
        </property>
    </bean>

    <!-- 视图解析器
    解析jsp解析,默认使用jstl标签,classpath下的得有jstl的包
     -->
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 配置jsp路径的前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!-- 配置jsp路径的后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

    
    <!-- 文件上传 -->
    <bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 设置上传文件的最大尺寸为5MB -->
        <property name="maxUploadSize">
            <value>5242880</value>
        </property>
    </bean>
    
    
    
    
</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>ssm_redpacket</display-name>
  
  <!-- 加载spring容器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/classes/spring/applicationContext-*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    
    <!-- springmvc前端控制器 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- contextConfigLocation配置springmvc加载的配置文件(配置处理器映射器、适配器等等) 如果不配置contextConfigLocation,默认加载的是/WEB-INF/servlet名称-serlvet.xml(springmvc-servlet.xml) -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/springmvc.xml</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 第一种:*.action,访问以.action结尾 由DispatcherServlet进行解析 第二种:/,所以访问的地址都由DispatcherServlet进行解析,对于静态文件的解析需要配置不让DispatcherServlet进行解析 
            使用此种方式可以实现 RESTful风格的url 第三种:/*,这样配置不对,使用这种配置,最终要转发到一个jsp页面时, 仍然会由DispatcherServlet解析jsp地址,不能根据jsp页面找到handler,会报错。 -->
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>
    
    <!-- post乱码过虑器 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
  
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
</web-app>

redpacket.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>参数</title>
        <!-- 加载Query文件-->
        <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.0.js">
        </script>
        <script type="text/javascript">
            $(document).ready(function () {
              //模拟30000个异步请求,进行并发
              var max = 30000;
              for (var i = 1; i <= max; i++) {
                  //jQuery的post请求,请注意这是异步请求
                  $.post({
                      //请求抢id为1的红包
                      //根据自己请求修改对应的url和大红包编号
                      url: "${pageContext.request.contextPath }/userRedPacket/grapRedPacket.action?redPacketId=1&userId=" + i,
                      //成功后的方法
                      success: function (result) {
                      }
                  });
              }
          });
        </script>
    </head>
    <body>
    
    haha
    
    </body>
</html>

模拟30000个用户同时进行抢红包操作

项目结构

此时进行高并发访问数据库不会出现超发问题,因为tomcat将数据库访问线程池中最大线程设置为了150,最小只有15,采用的还是BIO(阻塞型IO),刚并发性能很差,mysql毫无压力。。。

修改最大访问线程为5000,协议选择NIO,参考:

https://blog.csdn.net/dc282614966/article/details/81186783

此时就可以进行高并发访问了

访问http://localhost:8080/ssm_redpacket/redpacket.jsp
开始模拟高并发情况(注意使用火狐浏览器)

使用 SQL 去查询红包的库存、发放红包的总个数、总金额,我们发现了错误,红包总额为 20 万元,两万个小红包,结果发放了 200020元的红包, 20002 个红包。现有库存为-2,超出了之前的限定,这就是高并发的超发现象,这是一个错误的逻辑 。

SELECT
    a.id,
    a.amount,
    a.stock
FROM
    T_RED_PACKET a
WHERE
    a.id = 1
UNION ALL
    SELECT
        max(b.user_id),
        sum(b.amount),
        count(*)
    FROM
        T_USER_RED_PACKET b
    WHERE
        b.red_packet_id = 1;

一共使用了 50 秒的时间,完成 20002 个红包的抢夺,性能一般。。。但是逻辑上存在超发错误,还需要解决超发问题 。

SELECT
    (
        UNIX_TIMESTAMP(max(a.grab_time)) - UNIX_TIMESTAMP(min(a.grab_time)) 
    )  AS lastTime
FROM
    T_USER_RED_PACKET a;

超发问题解决思路:

超发现象是由多线程下数据不一致造成的,对于此类问题,如果采用数据库方案的话,主要通过悲观锁和乐观锁来处理,这两种方法的性能是不一样的。
接下来我们分别使用悲观锁、乐观锁、Redis+lua的方式来解决这个超发问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • ssm框架的学习小结,主要是初期环境的搭建配置以及信息的交互处理过程,非技术人员请绕道~有什么疑问可以关注我的公众...
    一只大黑猫阅读 4,577评论 0 2
  • 对于java中的思考的方向,1必须要看前端的页面,对于前端的页面基本的逻辑,如果能理解最好,不理解也要知道几点。 ...
    神尤鲁道夫阅读 806评论 0 0
  • 最近学了SSM框架(Spring,SpringMVC,Mybatis),学到了蛮多的,也遇到了很多坑,哈哈。今天重...
    拟定微笑阅读 6,196评论 4 7
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,460评论 0 4
  • 当我们说“人是一种读书的动物”,这意味着,读书是一种属于人的活动或事情,读书与做人几乎就是同一件事。人们常说,“想...
    耕耘三尺有天地阅读 362评论 0 3