超详细!利用SpringBoot+SpringCloud做一个问答项目(十)

目录

一、标签列表---前端页面【续】

二、通过Thymeleaf复用页面中的“碎片”

三、发布问题---持久层

四、发布问题---业务层

一、标签列表---前端页面【续】


目前,启动所有项目,通过 http://localhost/api-question/v1/tags 可以获取标签列表数据,通过 http://localhost 可以打开主页,且,在主页的偏顶部位置会显示模拟数据的“标签列表”,接下来,应该通过Vue向服务器端发送请求,获取真实的标签列表数据,然后将数据绑定到Vue属性中,使得网页中显示这些数据 :

let tagsApp = new Vue({
    el: '#tagsApp',
    data: {
        tags: []
    },
    methods: {
        loadTags: function () {
            // alert("准备加载标签列表……");
            $.ajax({
                url: '/api-question/v1/tags',
                success: function(json) {
                    tagsApp.tags = json.data;
                }
            });
        }
    },
    created: function () {
        this.loadTags();
    }
});

二、通过Thymeleaf复用页面中的“碎片”


目前,已经在“主页”显示了“标签列表”,在“我要提问”页面中,也会显示相同的区域!

由于2个页面都需要显示完全相同的区域,使用复制粘贴代码的方式即可实现,但是,复制粘贴代码是不易于整体维护的,当使用Thymeleaf来处理模版页面时,可以将页面中的某个区域设置为“碎片”,这个“碎片”是可以被任何其它由Thymeleaf处理的模版页面来引用的!

首先,需要在项目中添加Thymeleaf的依赖,参考代码为:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

如果需要使用Thymeleaf实现复用,首先,页面的显示就应该通过Thymeleaf来实现,目前,页面文件都是直接放在static文件夹下的,客户端发出请求时,也是直接访问static下的资源的,不会通过Thymeleaf,所以,先在resources 下创建templates文件夹,该文件夹是SpringBoot项目默认的“模版页面文件夹”,是会被SpringBoot自动配置的文件夹(如果在创建项目时就勾选了spring-boot-starter-web依赖,在创建项目时就会自动把这个templates文件夹创建出来),然后,把index.htmlquestion文件夹移动到templates文件夹下:

templates下的页面都是“模版页面”,是不可以被直接访问的,则需要通过控制器进行转发!在straw-gatewaycn.tedu.straw.gateway.controller包中创建SystemController控制器类,实现对“主页”、“我要提问”页面的请求的转发:

package cn.tedu.straw.gateway.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class SystemController {

    // http://localhost/index.html
    @GetMapping("/index.html")
    public String index() {
        // 当处理请求的方法的返回值是String时
        // -- 且没有使用@RestController注解,也没有使用@ResponseBody注解时
        // -- 返回的String就是视图名,或者,使用 redirect: 作为前缀表示重定向
        // 在SpringBoot中
        // -- 执行转发时,整合了Thymeleaf框架后
        // -- 默认的前缀就是 /templates/,默认的后缀是 .html
        // -- 与当前方法的返回值组合起来,将得到:
        // -- /templates/index.html
        return "index";
    }

    // http://localhost/question/create.html
    @GetMapping("/question/create.html")
    public String createQuestion() {
        return "question/create";
    }

}

然后,重启项目,此前的页面是可以正常显示的,对于用户的感受是没有变化的,但是,此前是直接访问网页文件的,现在已经改成了通过控制器转发到页面,是由Thymeleaf框架处理的!

接下来,先在“主页”中“显示标签列表”的区域设置为“碎片”:

然后,在目标位置,也就是在question/create.html页面中,在原本显示标签列表的位置,随意使用一个标签,将其“替换”为“碎片”即可:

三、发布问题---持久层


在发布问题时,需要同时向questionquestion_taguser_question这3张表中插入数据记录!

可以通过代码生成器自动生成与这3张表相关的类,方便后续使用。

首先,代码生成器生成的代码是基于Mybatis Plus框架的,所以,需要在straw-api-question项目中补充添加MyBatis Plus的依赖:

然后,在代码生成器项目中,先修改生成的代码的相关参数:

运行3次代码生成器,分别输入各数据表的名称,例如:

然后,将model包放到straw-commons项目中,将mapper包下的xml文件夹删除,将mapperservicecontrollerresources/mapper下的XML文件都移动到straw-api-question项目中。

复制后,需要调整mapperservice包中类和接口的import语句,调整XML文件中实体类的包名。

四、发布问题---业务层


先在cn.tedu.straw.api.question.dto包中创建PostQuestionDTO类,在类中声明客户端”发布问题“时会提交的参数:

package cn.tedu.straw.api.question.dto;

import lombok.Data;

import java.io.Serializable;

@Data
public class PostQuestionDTO implements Serializable {

    private String title;
    private String content;
    private Integer[] tagIds;
    private Integer[] teacherIds;
    
}

straw-api-user中的ex包剪切到straw-commons项目中去!

IQuestionService接口中声明“发布问题”的抽象方法:

public interface IQuestionService extends IService<Question> {

    /**
     * 发布问题
     *
     * @param postQuestionDTO 客户端提交的请求参数
     * @param userId          当前登录的用户id
     * @param userNickname    当前登录的用户昵称
     */
    void postQuestion(PostQuestionDTO postQuestionDTO, Integer userId, String userNickname);

}

QuestionServiceImpl中设计业务方法的实现步骤:

package cn.tedu.straw.api.question.service.impl;

import cn.tedu.straw.api.question.dto.PostQuestionDTO;
import cn.tedu.straw.api.question.mapper.QuestionMapper;
import cn.tedu.straw.api.question.mapper.QuestionTagMapper;
import cn.tedu.straw.api.question.mapper.UserQuestionMapper;
import cn.tedu.straw.commons.ex.InsertException;
import cn.tedu.straw.commons.model.Question;
import cn.tedu.straw.api.question.service.IQuestionService;
import cn.tedu.straw.commons.model.QuestionTag;
import cn.tedu.straw.commons.model.UserQuestion;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Arrays;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author tedu.cn
 * @since 2020-09-16
 */
@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements IQuestionService {

    @Autowired
    QuestionMapper questionMapper;
    @Autowired
    QuestionTagMapper questionTagMapper;
    @Autowired
    UserQuestionMapper userQuestionMapper;

    @Transactional
    @Override
    public void postQuestion(PostQuestionDTO postQuestionDTO, Integer userId, String userNickname) {
        // 创建当前时间对象now
        LocalDateTime now = LocalDateTime.now();

        // 处理客户端提交的tagIds
        String tagIdsString = Arrays.toString(postQuestionDTO.getTagIds());
        tagIdsString = tagIdsString.substring(1, tagIdsString.length() - 1);

        // 创建Question对象
        Question question = new Question()
            // 补全Question对象中的属性值:title			< 参数
            .setTitle(postQuestionDTO.getTitle())
            // 补全Question对象中的属性值:content		    < 参数
            .setContent(postQuestionDTO.getContent())
            // 补全Question对象中的属性值:userId			< 参数
            .setUserId(userId)
            // 补全Question对象中的属性值:userNickName	< 参数
            .setUserNickName(userNickname)
            // 补全Question对象中的属性值:status			< 0
            .setStatus(0)
            // 补全Question对象中的属性值:hits			< 0
            .setHits(0)
            // 补全Question对象中的属性值:isDelete		< 0
            .setIsDelete(0)
            // 补全Question对象中的属性值:tagIds			< 参数,Arrays.toString()
            .setTagIds(tagIdsString)
            // 补全Question对象中的属性值:gmtCreate		< 当前时间now
            .setGmtCreate(now)
            // 补全Question对象中的属性值:gmtModified	    < 当前时间now
            .setGmtModified(now);
        // 调用questionMapper.insert(question)插入数据,并获取返回值
        int rows = questionMapper.insert(question);
        // 判断返回值是否不为1
        if (rows != 1) {
            // 是:抛出InsertException
            throw new InsertException("发布问题失败!服务器忙,请稍后再次尝试!");
        }

        // 遍历参数tagIds
        for (int i = 0; i < postQuestionDTO.getTagIds().length; i++) {
            // -- 创建QuestionTag对象
            QuestionTag questionTag = new QuestionTag()
                // -- 补全QuestionTag对象中的属性值:questionId	< question.getId()
                .setQuestionId(question.getId())
                // -- 补全QuestionTag对象中的属性值:tagId		    < 在tagIds中被遍历到的元素
                .setTagId(postQuestionDTO.getTagIds()[i])
                // -- 补全QuestionTag对象中的属性值:gmtCreate	    < 当前时间now
                .setGmtCreate(now)
                // -- 补全QuestionTag对象中的属性值:gmtModified	< 当前时间now
                .setGmtModified(now);
            // -- 调用questionTagMapper.insert(questionTag)插入数据,并获取返回值
            rows = questionTagMapper.insert(questionTag);
            // -- 判断返回值是否不为1
            if (rows != 1) {
                // -- 是:抛出InsertException
                throw new InsertException("发布问题失败!处理问题的标签数据时出现未知错误,请联系系统管理员!");
            }
        }

        // 遍历参数teacherIds
        for (int i = 0; i < postQuestionDTO.getTeacherIds().length; i++) {
            // -- 创建UserQuestion对象
            UserQuestion userQuestion = new UserQuestion()
                // -- 补全UserQuestion对象中的属性值:userId		< 在teacherIds中被遍历到的元素
                .setUserId(postQuestionDTO.getTeacherIds()[i])
                // -- 补全UserQuestion对象中的属性值:questionId	< question.getId()
                .setQuestionId(question.getId())
                // -- 补全UserQuestion对象中的属性值:gmtCreate	< 当前时间now
                .setGmtCreate(now)
                // -- 补全UserQuestion对象中的属性值:gmtModified	< 当前时间now
                .setGmtModified(now);
            // -- 调用userQuestionMapper.insert(userQuestion)插入数据,并获取返回值
            rows = userQuestionMapper.insert(userQuestion);
            // -- 判断返回值是否不为1
            if (rows != 1) {
                // -- 是:抛出InsertException
                throw new InsertException("发布问题失败!处理回答问题的老师的数据时出现未知错误,请联系系统管理员!");
            }
        }
    }

}

完成后,在testcn.tedu.straw.api.question.service包中创建QuestionServiceTests测试类,编写并执行单元测试:

package cn.tedu.straw.api.question.service;

import cn.tedu.straw.api.question.dto.PostQuestionDTO;
import cn.tedu.straw.commons.ex.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
@Slf4j
public class QuestionServiceTests {

    @Autowired
    IQuestionService service;

    @Test
    void postQuestion() {
        try {
            PostQuestionDTO postQuestionDTO = new PostQuestionDTO()
                    .setTitle("数据库查询效率低下怎么解决")
                    .setContent("今天被面试官问倒了,好难受呀!")
                    .setTagIds(new Integer[]{2, 7, 9})
                    .setTeacherIds(new Integer[]{1, 3, 5});
            Integer userId = 20;
            String userNickName = "变形金刚";
            service.postQuestion(postQuestionDTO, userId, userNickName);
            log.debug("发布问题成功!");
        } catch (ServiceException e) {
            log.debug("发布问题失败!问题类型:{},问题原因:{}", e.getClass().getName(), e.getMessage());
        }
    }

}

附(一):关于链式写法

public class User {
	String username;
	String password;
	String from;
	
	public User(String username, String password, String from) {
		this.username = username;
		this.password = password;
		this.from = from;
	}
	
	public User setUsername(String username) {
		this.username = username;
		return this;
	}
	
	public User setPassword(String password) {
		this.password = password;
		return this;
	}
	
	public User setFrom(String from) {
		this.from = from;
		return this;
	}
	
}


User user = new User("Jack", "1234", "Chengdu");

User user = new User()
	.setUsername("Jack")
	.setPassword("1234")
	.setFrom("Chengdu");

User user = new User();
user.setUsername("Jack");
user.setPassword("1234");
user.setFrom("Chengdu");

附(二):基于JDBC的事务处理

总则:如果某个业务方法中执行了超过1次的增、删、改操作(例如执行了2次INSERT操作,或1次INSERT加1次DELETE操作等),必须在业务方法的声明之前添加@Transactional注解。

事务(Transaction):事务是数据库中能够保证多次连续执行的多个增、删、改操作能够全部执行成功,或全部执行失败的一种机制。

假设存在银行账户信息如下:

账户余额
路人甲1000
路人乙2000

假设需要实现转账“路人乙向路人甲转账500元”,需要执行的SQL语句大致是:

update 账户表 set 余额=余额-6000 where 账户='路人乙';
update 账户表 set 余额=余额+6000 where 账户='路人甲';

如果出现某种意外,导致以上第1条SQL语句执行成功,而第2条却无法执行或执行失败,则会出现数据安全问题!

使用事务机制就可以保证以上2条SQL语句要么全部执行成功,要么全部执行失败,无论是哪一种情况,都是可能接受的,数据安全并不会受到影响!

基于Spring JDBC的事务处理大致是:

try {
    开启事务:begin
    执行一系列的数据操作
    提交事务:commit
} catch (RuntimeException e) {
	回滚事务:rollback
}

在基于Spring JDBC的事务,事务的回滚标准默认是以“捕获到RuntimeException或其子孙类异常对象”为标准的!所以,在开发持久层的增、删、改功能时,都应该提供返回值,以表示“受影响的行数”,并且,在业务层中调用持久层的增、删、改方法时,需要及时获取返回的受影响行数,判断返回值是否与预期值不相符,如果不相符,则抛出RuntimeException或其子孙类异常的对象,为事务的回滚提供依据!

另外,基于Spring JDBC的事务默认是捕获RuntimeException来回滚的,也可以通过@Transactional注解的参数来配置如何回滚,例如:

@Transactional(rollbackFor = SQLException.class)

以上配置就表示出现SQLException时回滚!

另外,还可以将@Transactional注解添加在业务类的声明之前,则该类中所有的方法都将以事务的机制来执行,但是,没有这个必要性,也不推荐这样处理!

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值