[SpringBoot Project] SpringBoot+MyBatis+MySQL Computer Mall

I listened to Teacher Yuan’s development class at Station B and took some notes.

01-Project environment construction_bilibili_bilibili

Computer mall project based on springboot framework (1)_springboot mall project_Weightless outer space.'s blog-CSDN blog

Project environment setup

1. Project analysis

1. Project functions: login, registration, hot-selling products, user management (password, personal information, avatar, delivery address), shopping cart (display, add, delete), order module.
2. Development sequence: registration, login, user management, shopping cart, products, order module.
3. Development of a certain module

  • Persistence layer development: plan relevant SQL statements and configure them according to the settings of the front-end page
  • Business layer development: core function control, business operations and exception handling
  • Control layer development: accepting requests and processing responses
  • Front-end development: JS, Query, AJAX and other technologies to connect to the backend

2. Development environment

  • JDK: version 1.8 and above
  • maven: Configure to idea, version 3.6.1
  • Database: MariaDB, MySQL, version 5.1 and above required
  • Development platform: idea development

3. Build the project

1.Project name: store

2. Structure: com.cy.store

java web

my shoe

mysql driver

3. Resource files: under the resource folder (static holds static resources and templates)

4. Unit test: test.com.cy.store

5. Configure the connection source information of the data in the properties file

spring.datasource.url=jdbc:mysql://localhost:3306/store?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root

6. Create a store database

create database store character set utf8;

7. Test the connection:

  • Start the SpringBoot main class and see if there is corresponding Spring graphic output
  • Test whether the database connection can be loaded normally in the unit test class

Database connection pool:
1.DBCP
2.C3P0
3.Hikari: manages database connection objects

8. Check whether the static resources of the access project can be loaded normally. All static resources are copied to the static directory.

Note: idea has poor compatibility with JS code, and js code written may not be loaded properly. Five solutions: clear the project under maven of the project, install and redeploy; clear the cache with cash under the file option of the project; rebuild the project: rebuild option under the build option; restart the idea; restart the computer.

User registration

1. Create a data table

CREATE TABLE t_user (
	uid INT AUTO_INCREMENT COMMENT '用户id',
	username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',
	password CHAR(32) NOT NULL COMMENT '密码',
	salt CHAR(36) COMMENT '盐值',
	phone VARCHAR(20) COMMENT '电话号码',
	email VARCHAR(30) COMMENT '电子邮箱',
	gender INT COMMENT '性别:0-女,1-男',
	avatar VARCHAR(50) COMMENT '头像',
	is_delete INT COMMENT '是否删除:0-未删除,1-已删除',
	created_user VARCHAR(20) COMMENT '日志-创建人',
	created_time DATETIME COMMENT '日志-创建时间',
	modified_user VARCHAR(20) COMMENT '日志-最后修改执行人',
	modified_time DATETIME COMMENT '日志-最后修改时间',
	PRIMARY KEY (uid)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

2. Create the user’s entity class

2.1 Extract the public fields of the table through the table structure and place them in the base class of the entity class

public class baseEntity implements Serializable {
    private String createdUser;
    private Date createdTime; //import java.util.Data
    private String modifiedUser;
    private Date modifiedTime;

    //再手动声明get、set方法,equals() and hashCode();toString();

}

Why use implements Serializable: Because the entity class User is to be transmitted in the form of a stream on the network, it needs to be serialized ( why implements Serializable_IT_wjj's blog-CSDN blog )

2.2 To create a user's entity class, you need to inherit the baseEntity base class

public class User extends baseEntity implements Serializable {
    private Integer uid;
    private String username;
    private String password;
    private String salt;
    private String phone;
    private String email;
    private Integer gender;
    private String avatar;
    private Integer isDelete;
    
    //再手动声明get、set方法,equals() and hashCode();toString();
}

Any entity class requires: get and set methods, equals() and hashCode(), toString()

3. Registration-persistence layer

Operate the database through MyBatis. Working on the mybatis development process.

3.1 Plan the SQL statements that need to be executed

1. The user registration function is equivalent to inserting data.

insert into t_user (username, password) values (值列表)

2. When a user registers, first check whether the current user name exists. If it exists, registration cannot be performed, which is equivalent to a query statement.

select * from t_user where username=?

3.2 Design interfaces and abstract methods

1. Define the Mapper interface. Create a mapper package under the project directory structure, and create mapper interfaces according to different functional modules under this package. Create the UserMapper interface. The abstract methods of these two SQL statements must be defined in the interface.

package com.cy.store.mapper;
import com.cy.store.entity.User;

/** 处理用户数据操作的持久层接口 */
public interface UserMapper {
    /**
     * 插入用户数据
     * @param user 用户数据
     * @return 受影响的行数(在增删改都有受影响的行数作为返回值,可以根据返回值来判断是否执行成功)
     */
    Integer insert(User user);

    /**
     * 根据用户名查询用户数据
     * @param username 用户名
     * @return 找到对应的用户数据则返回用户的数据,如果没有找到的数据则返回null
     */
    User findByUsername(String username);
}

2. Configure the location of the mapper interface file in the startup class

//MapperScan annotation specifies the location of the Mapper interface path in the current project, and all interfaces will be automatically loaded when the project is started .

@MapperScan("com.cy.store.mapper")

package com.cy.store;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

import javax.servlet.MultipartConfigElement;

@Configuration
@SpringBootApplication
@MapperScan("com.cy.store.mapper")
public class StoreApplication {

    public static void main(String[] args) {
        SpringApplication.run(StoreApplication.class, args);
    }

    @Bean
    public MultipartConfigElement getMultipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        // DataSize dataSize = DataSize.ofMegabytes(10);
        // 设置文件最大10M,DataUnit提供5中类型B,KB,MB,GB,TB
        factory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES));
        factory.setMaxRequestSize(DataSize.of(10, DataUnit.MEGABYTES));
        // 设置总上传数据总大小10M
        return factory.createMultipartConfig();
    }
}

3.3Write mapping

1. Define the xml mapping file and associate it with the corresponding interface. All mapping files are resource files and need to be placed in the resource directory structure. Create a mapper folder (Directory) in this directory, and then store the mapper mapping file in this folder.

2. Create a mapping file corresponding to the interface and keep it consistent with the name of the interface. Create a UserMapper.xml file (file)

Namespace attributes: used to specify the current mapping file and which interface to map. The file path of the interface needs to be specified, and the complete path structure of the package needs to be marked.

<?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="com.cy.store.mapper.UserMapper">
    <resultMap id="UserEntityMap" type="com.cy.store.entity.User">
        <id column="uid" property="uid"/>
        <result column="is_delete" property="isDelete"/>
        <result column="created_user" property="createdUser"/>
        <result column="created_time" property="createdTime"/>
        <result column="modified_user" property="modifiedUser"/>
        <result column="modified_time" property="modifiedTime"/>
    </resultMap>

    <!-- 插入用户数据:Integer insert(User user) -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="uid">
        INSERT INTO
            t_user (username, password, salt, phone, email, gender, avatar, is_delete, created_user, created_time, modified_user, modified_time)
        VALUES
            (#{username}, #{password}, #{salt}, #{phone}, #{email}, #{gender}, #{avatar}, #{isDelete}, #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime})
    </insert>

    <!-- 根据用户名查询用户数据:User findByUsername(String username) -->
    <select id="findByUsername" resultMap="UserEntityMap">
        SELECT
            *
        FROM
            t_user
        WHERE
            username = #{username}
    </select>

</mapper>

3. The methods in the configuration interface correspond to SQL statements. It needs to be completed with the help of tags, insert/update/delete/selete, corresponding to the addition, deletion, modification and query operations of SQL statements.

insert

  • id attribute: Indicates the name of the method in the mapped interface. Write the SQL statement directly in the content of the tag.
  • useGeneratedKeys attribute: indicates that the value of a certain field is enabled to increment (the primary key is set to increment)
  • keyProperty attribute: Indicates which field in the table is used as the primary key to increment.

When select is executed, the result of the query is one object or multiple objects.

  • resultType represents the result set type of the query. You only need to specify the type of the corresponding mapped class and contains a complete package interface. resultType="com.cy.store.entity.User". This requires that the field names of the table be exactly the same as the names of the class attributes.
  • resultMap means to customize the mapping rules of the query result set when the field names of the table fields and the object attributes of the class are inconsistent. The resultMap id attribute indicates that a unique id value is assigned to the mapping rule, corresponding to the value of the resultMap="value of the id attribute" attribute. The value of the type attribute is a class, which indicates the mapping of the query results in the database to the entity class in Java for the result set. Fields that are inconsistent with the attributes of the table and the class are matched and specified. Fields with consistent names can be omitted. Do not write. column attribute : indicates the field name in the table; property attribute : indicates the attribute name in the class. The column attribute and property attribute cooperate to complete the mapping of inconsistent names. When defining mapping rules, the primary key cannot be omitted .

4. Register the location of the mapper file in the configuration file corresponding to properties. (fixed writing method)

mybatis.mapper-locations=classpath:mapper/*.xml

5. Unit testing: After each independent layer is written, a unit test method needs to be written to test the current function. Create a mapper package under the test package structure, and create functional tests for the persistence layer under this package.

@SpringBootTest: Indicates that the current class is marked as a test class and will not be packaged and sent together with the project.

@RunWith(SpringRunner.class): Indicates that to start this unit test class (if you don't write it - the unit test class cannot be run), you need to pass a fixed parameter, which must be the instance type of SpringRunner.

The unit test method ( which can be run independently without starting the entire project, can be used for unit testing, which improves the testing efficiency of the code ) must be modified by the @Test annotation, the return value type must be void, and the parameter list of the method cannot specify any type. , the access modifier of the method must be public.

package com.cy.store.mapper;

import com.cy.store.entity.User;
import com.cy.store.mapper.UserMapper;
import org.junit.Test;//记住
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

// @RunWith(SpringRunner.class)注解是一个测试启动器,可以加载Springboot测试注解
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTests {
    //idea有检测的功能,接口是不能直接创建Bean的(动态代理技术来解决)
    @Autowired
    private UserMapper userMapper;

    @Test
    public void insert() {
        User user = new User();
        user.setUsername("user05");
        user.setPhone("12345678910");
        Integer rows = userMapper.insert(user);
        System.out.println("rows=" + rows);
    }

    @Test
    public void findByUsername() {
        String username = "user02";
        User result = userMapper.findByUsername(username);
        System.out.println(result);
    }
}

4.Registration-Business Layer

4.1 Planning exceptions

1. RuntimeException exception, as a subclass of this exception, then define a specific exception type to inherit this exception. ServiceException is the base class of business layer exceptions. This exception inherits RuntimeException. The exception mechanism is established.

Alt+insert --override methods 

package com.cy.store.service.ex;

/** 业务异常的基类 */
public class ServiceException extends RuntimeException {
    public ServiceException() {
        super();
    }

    public ServiceException(String message) {
        super(message);
    }

    public ServiceException(String message, Throwable cause) {
        super(message, cause);
    }

    public ServiceException(Throwable cause) {
        super(cause);
    }

    protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

Specific exception types are defined in detail according to different business functions of the business layer, and uniformly inherit ServiceException.

2. When the user registers, an error may occur that the username is occupied, and an exception will be thrown: UsernameDuplicatedException.

3. While the data insertion operation is being performed, the server and database are down. Exception generated during insertion: InsertException.

4.2 Design interfaces and abstract methods

1. Create an IUserService interface (interface) under the service package.

package com.cy.store.service;
import com.cy.store.entity.User;

/** 处理用户数据的业务层接口 */
public interface IUserService {
    /**
     * 用户注册
     * @param user 用户数据的对象
     */
    void reg(User user);
}

@Service annotation: Handle the object of the current class to Spring for management, automatically create objects and maintain objects.

2. Create an implementation class UserServiceImpl class, which needs to implement this interface and implement abstract methods.

package com.cy.store.service.impl;

import java.util.UUID;
import com.cy.store.entity.User;
import com.cy.store.mapper.UserMapper;
import com.cy.store.service.IUserService;
import com.cy.store.service.ex.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.Date;

/** 处理用户数据的业务层实现类 */
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public void reg(User user) {
        // 根据参数user对象获取注册的用户名
        String username = user.getUsername();
        // 调用持久层的User findByUsername(String username)方法,根据用户名查询用户数据
        User result = userMapper.findByUsername(username);
        // 判断查询结果是否不为null
        if (result != null) {
            // 是:表示用户名已被占用,则抛出UsernameDuplicateException异常
            throw new UsernameDuplicateException("尝试注册的用户名[" + username + "]已经被占用");
        }

        // 创建当前时间对象
        Date now = new Date();
        // 补全数据:加密后的密码。盐值是一个随机的字符串。
        String salt = UUID.randomUUID().toString().toUpperCase();
        String md5Password = getMd5Password(user.getPassword(), salt);
        user.setPassword(md5Password);
        // 补全数据:盐值
        user.setSalt(salt);
        // 补全数据:isDelete(0)
        user.setIsDelete(0);
        // 补全数据:4项日志属性
        user.setCreatedUser(username);
        user.setCreatedTime(now);
        user.setModifiedUser(username);
        user.setModifiedTime(now);

        // 表示用户名没有被占用,则允许注册
        // 调用持久层Integer insert(User user)方法,执行注册并获取返回值(受影响的行数)
        Integer rows = userMapper.insert(user);
        // 判断受影响的行数是否不为1
        if (rows != 1) {
            // 是:插入数据时出现某种错误,则抛出InsertException异常
            throw new InsertException("添加用户数据出现未知错误,请联系系统管理员");
        }
    }

        /**
     * 执行密码加密
     * @param password 原始密码
     * @param salt 盐值
     * @return 加密后的密文
     */
    private String getMd5Password(String password, String salt) {
        /*
         * 加密规则:
         * 1、无视原始密码的强度
         * 2、使用UUID作为盐值,在原始密码的左右两侧拼接
         * 3、循环加密3次
         */
        for (int i = 0; i < 3; i++) {
            password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase();
        }
        return password;
    }

}

3. Create a UserServiceTests class under the unit test package and add unit testing functionality to this class.

package com.cy.store.service;

import com.cy.store.entity.User;
import com.cy.store.service.ex.ServiceException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTests {
    @Autowired
    private IUserService iUserService;

    @Test
    public void reg() {
        try {
            User user = new User();
            user.setUsername("lower");
            user.setPassword("123456");
            
            iUserService.reg(user);
            System.out.println("注册成功!");
        } catch (ServiceException e) {
            //获取类的对象,再获取类的名称
            System.out.println("注册失败!" + e.getClass().getSimpleName());
            //获取异常的具体描述信息
            System.out.println(e.getMessage());
        }
    }
}

5. Registration-Control Layer

Receive requests and process responses.

5.1 Create response

Status code, status description information, data. This part of the function is encapsulated in a class, and this class is used as the return value of the method and returned to the front-end browser.

package com.cy.store.util;
import java.io.Serializable;

/**
 * 响应结果类
 * Json格式的数据进行相应
 * @param <E> 响应数据的类型
 */
public class JsonResult<E> implements Serializable {
    /** 状态码 */
    private Integer state;
    /** 状态描述信息 */
    private String message;
    /** 数据 */
    private E data;

    public JsonResult() {
        super();
    }

    public JsonResult(Integer state) {
        super();
        this.state = state;
    }

    /** 出现异常时调用 */
    public JsonResult(Throwable e) {
        super();
        // 获取异常对象中的异常信息
        this.message = e.getMessage();
    }

    public JsonResult(Integer state, E data) {
        super();
        this.state = state;
        this.data = data;
    }

    public Integer getState() {
        return state;
    }

    public void setState(Integer state) {
        this.state = state;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public E getData() {
        return data;
    }

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

5.2 Design Request

Design the request based on the current business function module.

  • Request path:/users/reg
  • Request parameters: User user
  • Request type: POST (with sensitive data), GET
  • Request result: JsonResult<void>

5.3 Handling requests

Create a UserController class corresponding to the control layer, which depends on the interface of the business layer.

/** 处理用户相关请求的控制器类 */
@RestController
@RequestMapping("users")
public class UserController extends BaseController {
    @Autowired
    private IUserService userService;

    /*
    @RequestMapping("reg")
    public JsonResult<Void> reg(User user) {
        // 调用业务对象执行注册
        userService.reg(user);
        // 返回
        return new JsonResult<Void>(OK);
    }
    */
    
    @RequestMapping("reg")
    public JsonResult<Void> reg(User user) {
        // 创建返回值(创建响应结果对象)
        JsonResult<Void> result = new JsonResult<Void>();
        try {
            // 调用业务对象执行注册
            userService.reg(user);
            // 响应成功
            result.setState(200);
        } catch (UsernameDuplicateException e) {
            // 用户名被占用
            result.setState(4000);
            result.setMessage("用户名已经被占用");
        } catch (InsertException e) {
            // 插入数据异常
            result.setState(5000);
            result.setMessage("注册失败,请联系系统管理员");
        }
        return result;
    }
}

 @RestController

@RestController Detailed explanation_A blog that looks at code from another angle-CSDN blog

@RestController is a combination of @controller and @ResponseBody

@Controller injects the currently decorated class into the SpringBoot IOC container, so that this class is instantiated when the project where the class is located is run.
@ResponseBody In short, its function refers to the data returned by all API interfaces in this class. Regardless of whether your corresponding method returns a Map or other Object, it will respond to the data in the form of a Json string to the front end.

The @RequestMapping annotation can associate a request with the controller method that handles the request and establish a mapping relationship.

SpringMvc---@RequestMapping annotation and its attributes_@requestmapping defines array_Miao Lin's blog-CSDN blog

5.4 Control layer optimization design

Extract a parent class at the control layer, and handle exception operations uniformly in this parent class. Write a BaseController class to handle exceptions uniformly.

@ExceptionHandler is used to uniformly handle thrown exceptions

/** 控制器类的基类 */
public class BaseController {
    /** 操作成功的状态码 */
    public static final int OK = 200;

    
    /** @ExceptionHandler用于统一处理方法抛出的异常 */
    //请求处理方法,这个方法的返回值就是需要传递给前端的数据
    //自动将异常对象传递给此方法的参数列表上
    //当项目中产生了异常,会被统一拦截到此方法中,这个方法此时充当请求处理方法,方法的返回值直接给到前端
    @ExceptionHandler({ServiceException.class, FileUploadException.class})
    public JsonResult<Void> handleException(Throwable e) {
        JsonResult<Void> result = new JsonResult<Void>(e);
        if (e instanceof UsernameDuplicateException) {
            result.setState(4000);
        } 
        } else if (e instanceof InsertException) {
            result.setState(5000);
        }
        return result;
    }
}

The reg() method has been restructured.

@RequestMapping("reg")
    public JsonResult<Void> reg(User user) {
        // 调用业务对象执行注册
        userService.reg(user);
        // 返回
        return new JsonResult<Void>(OK);
    }

6. Registration-front-end page

1. Write the method to send the request on the \store\src\main\resources\static\web\register.html page, click on the event to complete, first select the corresponding button ($ ("selector")), and then add the click Event, the $.ajax() function sends an asynchronous request.

2.JQuery encapsulates a function called the $.ajax() function. By calling the ajax() function through the object, related requests can be loaded asynchronously . It relies on an object XHR (XmlHttpResponse) provided by JavaScript, which encapsulates this object.

3.How to use ajax(). A method body needs to be passed as a parameter of the method. A pair of curly brackets is called the method body. Ajax receives multiple parameters. English commas are required to separate parameters between parameters. English colons are used to separate each group of parameters. The components of a parameter are the parameter name (which cannot be defined arbitrarily) and the value of the parameter (requires the use of a string to express "double quotes"), and there is no requirement for the declaration order of the parameters. Grammatical structures:

$.ajax(fun());

function fun(){

        //TODO

}

$.ajax({
    url:"",
    type:"",
    data:"",   
    dataType:"",
    success:function(){

    },
    error:function(){

    },
});

4. The meaning of ajax() function parameters: 

parameter Function description
url Identifies the address of the request (url address) and cannot contain the content of the parameter list. For example: url:"localhost:8080/users/reg"
type The type of request (GET, POST). For example: type:"POST"
data Data submitted to the specified request URL address. For example: data:"username=tom&pwd=123"
dataType The type of data submitted. The data type is generally specified as json type. dataType:"json"

success

When the server responds to the client normally, it will automatically call the success parameter method and pass the data returned by the server to the parameters of this method in the form of parameters.

error

When the server does not respond normally to the client, the method with the error parameter will be automatically called, and the data returned by the server will be passed to the parameters of this method in the form of parameters.

5. The js code can be stored independently in a file with the suffix js, or declared in a script tag.

<script type="text/javascript">
	//1.监听注册按钮是否被点击,如果被点击可以执行一个方法
	$("#btn-reg").click(function() {
        //麻烦 动态获取表单中控件的数据
        //麻烦 let username = $("#username").val();
        //麻烦 let pwd = $("#password").val(); 
        //console.log($("#from-reg").serialize());//输出表单看看       

		//2.发送ajax()的异步请求来完成用户的注册功能
		$.ajax({
			url: "/users/reg",
			type: "POST",
			data: $("#form-reg").serialize(),
            //麻烦 data: "username="+username+"&password="+pwd,
			dataType: "json",
			success: function(json) {
				if (json.state == 200) {
					alert("注册成功!");
					// location.href = "login.html";
				} else {
					alert("注册失败!" + json.message);
				}
			}
            error: function(xhr){
                alert("注册时产生未知错我!" + xhr.status);
            }
		});
	});
</script>

6. The js code cannot be parsed and executed by the server normally, which is reflected in the fact that there is no response when clicking the button on the page. Solution:

  • Clear the project under maven of the project, install and redeploy
  • Cash clear cache under the file option of the project
  • Rebuild the project: rebuild option under build option
  • Restart idea
  • Restart the computer



The difference between PostMapping and GetMapping

The difference between PostMapping and GetMapping, personal understanding of usage scenarios_BACKLS's blog-CSDN blog

Guess you like

Origin blog.csdn.net/weixin_43570470/article/details/132676622