美文网首页从零开发电商网站
1.2 建立后台springboot工程

1.2 建立后台springboot工程

作者: 小肥爬爬 | 来源:发表于2020-02-10 23:09 被阅读0次

    后台网站包括springboot项目工程和vue项目工程, 先来建立springboot工程. 可以参考我之前写的这篇: springboot入门

    pom文件

    注意工程使用的是mysql数据库, 所以要将pom.xml 文件改成:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <version>4.0.0</version>
        <groupId>org.diego</groupId>
        <artifactId>diegomall</artifactId>
        <packaging>jar</packaging>
        
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.2.RELEASE</version>
            <relativePath /> <!-- lookup parent from repository -->
        </parent>
    
        <build>
            <plugins>
                <!--spring boot打包的话需要指定一个唯一的入口 -->      
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <!-- 指定该Main Class为全局的唯一入口 -->
                        <mainClass>org.diego.mall.StartApp</mainClass>
                        <layout>ZIP</layout>
                        <profiles>
                            <profile>dev</profile>
                            <profile>prod</profile>
                        </profiles>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackage</goal><!--可以把依赖的包都打包到生成的Jar包中 -->
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
                
                <!-- 打包时跳过测试 tests  -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                      <skipTests>true</skipTests>
                    </configuration>
                  </plugin>
            </plugins>
        </build>
        
    
        <dependencies>
        
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>
                
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
            </dependency>
            
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            
    
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>2.1</version>
            </dependency>
    
            <dependency>
                <groupId>commons-beanutils</groupId>
                <artifactId>commons-beanutils</artifactId>
                <version>1.8.3</version>
            </dependency>
            
    
            <dependency>
                <groupId>org.jsoup</groupId>
                <artifactId>jsoup</artifactId>
                <version>1.9.2</version>
            </dependency>
            
        </dependencies>
    </project>
    
    

    工程结构解释

    工程的包图如下所示


    image.png

    dev目录: 存放一些开发过程中使用的代码
    config: 存放springboot可能的配置
    controller: 存放springmvc 的controller
    dao目录: 存放dao类
    pojo目录: 存放pojo类,一个pojo代表着一张数据表
    service类: 存放service, service的方法都开启着事务管理
    utils: 存放一些通用的工具方法类
    vo: 存放值对象, 这些对象通常会在业务层传输来往.

    这是一个经典的mvc工程, 做java的同学都很熟悉, 就不多解释了.

    mybatis增删改查功能的封装

    众所周知, java项目很容易引起类膨胀, 其实二八法则同样适合于软件项目. 80%的功能都是重复性的, 没必要增加太多的类来实现它们. 所以我做了mybatis通用功能的封装, 它们包括 CommonDao, CommonDaoDaoMapper.xml, ISingleTableService 和 AbstractSingleTableService 4个类. 我没有一下子把项目的代码都copy过来, 我想随着这个博客的进程而逐步把代码加进来, 这样也容易帮助自己思考优化.

    封装功能的实现

    CommonDao

    package org.diego.mall.dao;
    
    import java.util.Map;
    import java.util.TreeMap;
    
    import org.apache.ibatis.annotations.Param;
    
    /**
     * @author cys
     */
    
    public interface CommonDao {
            
        // 插入一条数据
        Long insertOne(@Param("tableName")String tableName, @Param("dataMap")TreeMap<String, Object> dataMap);
    
        // 查找字段第一条数据
        Map getFirst(@Param("tableName")String tableName, @Param("fieldName")String fieldName, @Param("fieldValue")Object fieldValue);
    
        // 根据sql语句查找第一条数据
        Map getFirstBySql(@Param("tableName")String tableName, @Param("sqlFilter")String sqlFilter);
        
    }
    
    

    CommonDaoMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="org.diego.mall.dao.CommonDao">
      
      <!-- ============================ 插入单条数据  -->
      <insert id="insertOne" flushCache="true"  useGeneratedKeys="true" keyProperty="dataMap.id">  
         
        insert into ${tableName} 
        
        <foreach collection="dataMap.keys" separator="," open="(" close=")" index="index" item="item">
            `${item}`
        </foreach>
        
        values  
        
        <foreach collection="dataMap.values" separator="," open="(" close=")" index="index" item="item">
            #{item}
        </foreach>
        
      </insert>
      
      <!-- ============================ 返回符合字段条件的第一条数据  -->
        <select id="getFirst" resultType="HashMap">  
           select * from ${tableName} where `${fieldName}` = #{fieldValue} limit 0,1
        </select> 
        
        
      <!-- ============================ 返回符合字段条件的第一条数据  -->
        <select id="getFirstBySql" resultType="HashMap">  
           select * from ${tableName} where 1=1 
            
            <if test="sqlFilter != null">
                ${sqlFilter}
            </if> 
            
             limit 0,1
           
        </select>  
      
    </mapper>
    

    ISingleTableService

    package org.diego.mall.service;
    
    public interface ISingleTableService<T> {
        
        // 根据字段 获取对象, 如果有多个返回第一个
        T getFirst(String name, Object value);
        
        // 插入一条数据
        Long insertOne(Long userId,T entity, String[] inputAvoidFields) throws Exception;
        
        // 根据sql 获取对象, 如果有多个返回第一个
        T getFirstBySql(String sqlFilter);
    }
    
    

    AbstractSingleTableService

    package org.diego.mall.service.impl;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.ParameterizedType;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.Map;
    import java.util.TreeMap;
    
    import org.diego.mall.dao.CommonDao;
    import org.diego.mall.service.ISingleTableService;
    import org.diego.mall.utils.RefUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Autowired;
    
    /**
     * 
     * 这个基类的目的是减少大量重复的增删改查表的操作, 让mapper.xml真正保存有用的业务sql 
     * 
     * 1) 对于那些单表增删改查的业务模块, 继承这个类之后基本上不需要写新建/更新/单表查询的service代码了. 
     * 2) 默认实现了一个无关联表的分页查询. 如果有关联的分页, 还是要自己实现.
     * 
     * @author cys
     *
     */
    public class AbstractSingleTableService<T> implements ISingleTableService<T>, InitializingBean {
    
        public static final String CREATE_TIME = "createTime";
        public static final String UPDATE_TIME = "updateTime";
        public static final String CREATE_USER_ID = "createUserId";
        public static final String UPDATE_USER_ID = "updateUserId";
    
        private Logger logger = LoggerFactory.getLogger(AbstractSingleTableService.class);
    
        protected String tableName = null;
        protected Class<T> clazz = null;
        protected List<String> defaultUpdateAvoidFields;    // 默认在新建/修改时忽略的字段(如外键字段)
        
        @Autowired
        CommonDao commonDao;
    
        @Override
        public void afterPropertiesSet() throws Exception {
    
            clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    
    //      System.out.print("=============== simple name: " + clazz.getSimpleName());
    //      System.out.print("=============== con name: " + clazz.getCanonicalName());
            this.tableName = clazz.getSimpleName();
            
            // 不新建/修改某些字段
            this.defaultUpdateAvoidFields = new ArrayList<>();
            Map<String,Method> fieldMap = RefUtils.findFieldWithGetter(clazz.newInstance());
            for(String name : fieldMap.keySet()) {
                if(name.startsWith("ex") || name.startsWith("EX")) {
                    logger.debug("== ex field: " + name);
                    this.defaultUpdateAvoidFields.add(name);
                }
            }
        }
    
    
        @Override
        public T getFirst(String fieldName, Object fieldValue) {
            Map data = commonDao.getFirst(this.tableName, fieldName, fieldValue);
            if(data == null)return null;
            return (T) RefUtils.copyFromMapToObject(data, this.clazz, false, null);
        }
    
        @Override
        public Long insertOne(Long userId,T entity, String[] inputAvoidFields) throws Exception {
            // 自动将ex字段排除
            String[] avoidFields = RefUtils.checkAndMakeProperAvoidFields(this.defaultUpdateAvoidFields, inputAvoidFields);
            this.setCreateInfoIfExist(userId, entity);
            TreeMap<String, Object> dataMap = RefUtils.copyFieldsToMap(entity, false, avoidFields, null);
            commonDao.insertOne(this.tableName, dataMap);
            if(dataMap.get("id") == null) return null;
            Long id = Long.parseLong(dataMap.get("id").toString());
            return id;
        }
    
        private void setCreateInfoIfExist(Long userId, T entity) {
            try {
                Map<String, Method> dmap = RefUtils.findFieldWithSetter(entity);
                if (userId !=null && dmap.containsKey(CREATE_USER_ID)) {
                    Method m = dmap.get(CREATE_USER_ID);
                    m.invoke(entity, userId);
                }
                if (dmap.containsKey(CREATE_TIME)) {
                    Method m = dmap.get(CREATE_TIME);
                    m.invoke(entity, new Date());
                }
            } catch (Exception e) {
                logger.error("", e);
            }
        }
    
        private void setUpdateInfoIfExist(Long userId, T entity) {
            try {
                Map<String, Method> dmap = RefUtils.findFieldWithSetter(entity);
                if (dmap.containsKey(UPDATE_USER_ID)) {
                    Method m = dmap.get(UPDATE_USER_ID);
                    m.invoke(entity, userId);
                }
                if (dmap.containsKey(UPDATE_TIME)) {
                    Method m = dmap.get(UPDATE_TIME);
                    m.invoke(entity, new Date());
                }
            } catch (Exception e) {
                logger.error("", e);
            }
        }
    
        @Override
        public T getFirstBySql(String sqlFilter) {
            Map data = commonDao.getFirstBySql(this.tableName, sqlFilter);
            if(data == null)return null;
            return (T) RefUtils.copyFromMapToObject(data, this.clazz, false, null);
        }
    
    }
    
    

    通过这4个类, 就能省去大部分单表数据的增删改查了.

    这种做法的缺点

    凡事有利弊. 类膨胀的坏处是项目文件变多, 但哪个模块的方法在哪个类, 需求变更以后方法该加到哪里都一目了然. 上述这种单表的封装类, 无法解决多表联合查询, 无法实现复杂的业务. 如果有新人加入到新的项目, 他很容易把代价加到不应该加的地方.

    用了这种办法虽然省了重复的工作量, 但必须有很好的项目文档规范作为约束, 这算是这种做法的缺点吧.

    相关文章

      网友评论

        本文标题:1.2 建立后台springboot工程

        本文链接:https://www.haomeiwen.com/subject/nfozxhtx.html