【Java项目实战】牛客网论坛项目2 -MyBatis介绍与首页搭设

MyBatis 入门

数据库配置

安装 mysql5.7 或者 mysql8 数据库都可以无所谓

然后找到牛客提供的数据库 sql 文件
在该文件目录下打开命令行

依次输入:
mysql -u root -p
create database community
use community
source ./init-schema.sql

此时就自动把所有要用到的数据表导进去了,但是目前表中仍然没有任何数据,故需要添加对应数据

开启 navicat,连接到 community 数据库,再把 init_data.sql 拖拽进去就可以导入所有数据了


连接配置

你需要关注的就是下面几个配置项:

  • spring.datasource.url
  • spring.datasource.username
  • spring.datasource.password
  • spring.datasource.driver-class-name

代码清单:application.properties

server.port=10086
# 指定应用的端口号为 10086

server.servlet.context-path=/community
# 指定应用的上下文路径为 "/community",即应用的根路径为 "/community"

# ThymeleafProperties
spring.thymeleaf.cache=false
# 配置 Thymeleaf 模板引擎的缓存为关闭状态,方便开发阶段的模板修改和调试

# DataSourceProperties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 数据库驱动类

spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
# 数据库连接URL,指定数据库为localhost的3306端口上的community数据库,使用UTF-8字符编码,禁用SSL,时区设置为Hongkong

spring.datasource.username=root
# 数据库用户名

spring.datasource.password=123456
# 数据库密码

spring.datasource.type=com.zaxxer.hikari.HikariDataSource
# 数据源类型为HikariDataSource,使用HikariCP连接池

spring.datasource.hikari.maximum-pool-size=15
# 连接池的最大连接数为15

spring.datasource.hikari.minimum-idle=5
# 连接池的最小空闲连接数为5

spring.datasource.hikari.idle-timeout=30000
# 连接的最大空闲时间为30秒

# MybatisProperties
mybatis.mapper-locations=classpath:mapper/*.xml
# MyBatis映射文件的位置,使用classpath:mapper/*.xml表示在类路径下的mapper目录中查找映射文件

mybatis.type-aliases-package=com.zhiller.community.zhillercommunity.entity
# MyBatis实体类的包名,用于自动扫描实体类的别名

mybatis.configuration.useGeneratedKeys=true
# 配置MyBatis使用自动生成的主键

mybatis.configuration.mapUnderscoreToCamelCase=true
# 配置MyBatis将下划线命名的数据库字段映射为驼峰命名的Java属性

注意,如果你使用的是 mysql5,那么对应的数据连接池就要写
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
如果是 mysql8,就写
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver


User Model

定义查询对象

对象是一个 User 实体,它对应 community 数据库里的数据表 user

代码清单:entity/User.java

package com.zhiller.community.zhillercommunity.entity;

import java.util.Date;

public class User {
    
    

    private int id;
    private String username;
    private String password;
    private String salt;
    private String email;
    private int type;
    private int status;
    private String activationCode;
    private String headerUrl;
    private Date createTime;

    public int getId() {
    
    
        return id;
    }

    public void setId(int id) {
    
    
        this.id = id;
    }

    public String getUsername() {
    
    
        return username;
    }

    public void setUsername(String username) {
    
    
        this.username = username;
    }

    public String getPassword() {
    
    
        return password;
    }

    public void setPassword(String password) {
    
    
        this.password = password;
    }

    public String getSalt() {
    
    
        return salt;
    }

    public void setSalt(String salt) {
    
    
        this.salt = salt;
    }

    public String getEmail() {
    
    
        return email;
    }

    public void setEmail(String email) {
    
    
        this.email = email;
    }

    public int getType() {
    
    
        return type;
    }

    public void setType(int type) {
    
    
        this.type = type;
    }

    public int getStatus() {
    
    
        return status;
    }

    public void setStatus(int status) {
    
    
        this.status = status;
    }

    public String getActivationCode() {
    
    
        return activationCode;
    }

    public void setActivationCode(String activationCode) {
    
    
        this.activationCode = activationCode;
    }

    public String getHeaderUrl() {
    
    
        return headerUrl;
    }

    public void setHeaderUrl(String headerUrl) {
    
    
        this.headerUrl = headerUrl;
    }

    public Date getCreateTime() {
    
    
        return createTime;
    }

    public void setCreateTime(Date createTime) {
    
    
        this.createTime = createTime;
    }

    @Override
    public String toString() {
    
    
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", salt='" + salt + '\'' +
                ", email='" + email + '\'' +
                ", type=" + type +
                ", status=" + status +
                ", activationCode='" + activationCode + '\'' +
                ", headerUrl='" + headerUrl + '\'' +
                ", createTime=" + createTime +
                '}';
    }

}

UserMapper

配置 mybatis 的第一步就是需要定义 Mapper

mapper 用于设置操作数据库的方法

代码清单:/dao/UserMapper.java

package com.zhiller.community.zhillercommunity.dao;

import com.zhiller.community.zhillercommunity.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    
    
    User selectById(int id);

    User selectByName(String username);

    User selectByEmail(String email);

    int insertUser(User user);

    int updateStatus(int id, int status);

    int updateHeader(int id, String headerUrl);

    int updatePassword(int id, String password);
}


Mapper xml

既然设置了方法,那么就需要配置方法对应的 SQL 查询语句

mybatis 简化了这一操作,使用 xml 来配置所有的 SQL 语句
一个 XML 配置文件对应一个 mapper 文件


代码清单:resource/mapper/user-mapper.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="com.zhiller.community.zhillercommunity.dao.UserMapper">
    <!-- 定义插入字段 -->
    <sql id="insertFields">
        username, password, salt, email, type, status, activation_code, header_url, create_time
    </sql>

    <!-- 定义查询字段 -->
    <sql id="selectFields">
        id, username, password, salt, email, type, status, activation_code, header_url, create_time
    </sql>

    <!-- 根据id查询用户 -->
    <select id="selectById" resultType="User">
        select <include refid="selectFields"></include>
        from user
        where id = #{id}
    </select>

    <!-- 根据用户名查询用户 -->
    <select id="selectByName" resultType="User">
        select <include refid="selectFields"></include>
        from user
        where username = #{username}
    </select>

    <!-- 根据邮箱查询用户 -->
    <select id="selectByEmail" resultType="User">
        select <include refid="selectFields"></include>
        from user
        where email = #{email}
    </select>

    <!-- 插入用户 -->
    <insert id="insertUser" parameterType="User" keyProperty="id">
        insert into user (<include refid="insertFields"></include>)
        values(#{username}, #{password}, #{salt}, #{email}, #{type}, #{status}, #{activationCode}, #{headerUrl}, #{createTime})
    </insert>

    <!-- 更新用户状态 -->
    <update id="updateStatus">
        update user set status = #{status} where id = #{id}
    </update>

    <!-- 更新用户头像 -->
    <update id="updateHeader">
        update user set header_url = #{headerUrl} where id = #{id}
    </update>

    <!-- 更新用户密码 -->
    <update id="updatePassword">
        update user set password = #{password} where id = #{id}
    </update>
</mapper>

测试 MyBatis

依据主测试类编写专属于 mybatis 的测试类

如果下方代码运行成功,则证明数据库链接正确!

package com.zhiller.community.zhillercommunity;

import com.zhiller.community.zhillercommunity.dao.UserMapper;
import com.zhiller.community.zhillercommunity.entity.User;
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.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = ZhillerCommunityApplication.class)
public class MapperTest {
    
    
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectUser() {
    
    
        // 根据id查询用户
        User user = userMapper.selectById(101);
        System.out.println(user);

        // 根据用户名查询用户
        user = userMapper.selectByName("liubei");
        System.out.println(user);

        // 根据邮箱查询用户
        user = userMapper.selectByEmail("[email protected]");
        System.out.println(user);
    }

    @Test
    public void testInsertUser() {
    
    
        // 创建一个新用户对象
        User user = new User();
        user.setUsername("test");
        user.setPassword("123456");
        user.setSalt("abc");
        user.setEmail("[email protected]");
        user.setHeaderUrl("http://www.nowcoder.com/101.png");
        user.setCreateTime(new Date());

        // 插入用户
        int rows = userMapper.insertUser(user);
        System.out.println(rows);
        System.out.println(user.getId());
    }

    @Test
    public void updateUser() {
    
    
        // 更新用户状态
        int rows = userMapper.updateStatus(150, 1);
        System.out.println(rows);

        // 更新用户头像
        rows = userMapper.updateHeader(150, "http://www.nowcoder.com/102.png");
        System.out.println(rows);

        // 更新用户密码
        rows = userMapper.updatePassword(150, "hello");
        System.out.println(rows);
    }
}

首页制作

DiscussPost

根据上一节所述经验,我们应该能很快根据数据库中结构构建对应的 entity

故依葫芦画瓢,写出 DiscussPost 数据表的 entity

代码清单:DiscussPost.java

package com.zhiller.community.zhillercommunity.entity;

import java.sql.Date;

public class DiscussPost {
    
    
    private int id;
    private int userId;
    private String title;
    private String content;
    private int type;
    private int status;
    private Date createTime;
    private int commentCount;
    private double score;

    public int getId() {
    
    
        return id;
    }

    public void setId(int id) {
    
    
        this.id = id;
    }

    public int getUserId() {
    
    
        return userId;
    }

    public void setUserId(int userId) {
    
    
        this.userId = userId;
    }

    public String getTitle() {
    
    
        return title;
    }

    public void setTitle(String title) {
    
    
        this.title = title;
    }

    public String getContent() {
    
    
        return content;
    }

    public void setContent(String content) {
    
    
        this.content = content;
    }

    public int getType() {
    
    
        return type;
    }

    public void setType(int type) {
    
    
        this.type = type;
    }

    public int getStatus() {
    
    
        return status;
    }

    public void setStatus(int status) {
    
    
        this.status = status;
    }

    public Date getCreateTime() {
    
    
        return createTime;
    }

    public void setCreateTime(Date createTime) {
    
    
        this.createTime = createTime;
    }

    public int getCommentCount() {
    
    
        return commentCount;
    }

    public void setCommentCount(int commentCount) {
    
    
        this.commentCount = commentCount;
    }

    public double getScore() {
    
    
        return score;
    }

    public void setScore(double score) {
    
    
        this.score = score;
    }

    @Override
    public String toString() {
    
    
        return "DiscussPost{" +
                "id=" + id +
                ", userId=" + userId +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", type=" + type +
                ", status=" + status +
                ", createTime=" + createTime +
                ", commentCount=" + commentCount +
                ", score=" + score +
                '}';
    }
}

既然我们要查询,则必然需要调用 mybatis

所以再创建一个 mapper 接口来表示我们将要使用到的 SQL 方法

代码清单:/dao/DiscussPostMapper.java

package com.zhiller.community.zhillercommunity.dao;

import com.zhiller.community.zhillercommunity.entity.DiscussPost;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface DiscussPostMapper {
    
    
    // 查询所有的DiscussPost
    List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);

    // @Param注解用于给参数取别名,
    // 如果只有一个参数,并且在<if>里使用,则必须加别名.
    int selectDiscussPostRows(@Param("userId") int userId);
}

别忘了在 resource 文件下注册对应的 mapper 配置文件

代码清单:resource/mapper/discusspost-mapper.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="com.zhiller.community.zhillercommunity.dao.DiscussPostMapper">
    <sql id="selectFields">
        id, user_id, title, content, type, status, create_time, comment_count, score
    </sql>

    <select id="selectDiscussPosts" resultType="DiscussPost">
        select
        <include refid="selectFields"/>
        from discuss_post
        where status != 2

        <!-- 注意这里的if用于检测是否填写了userId参数 -->
        <!-- 当我们在mapper接口中使用@Param定义自定义名称参数时,一定要使用if标签进行判断! -->
        <if test="userId!=0">
            and user_id = #{userId}
        </if>
        order by type desc, create_time desc
        limit #{offset}, #{limit}
    </select>

    <select id="selectDiscussPostRows" resultType="int">
        select count(id)
        from discuss_post
        where status != 2

        <!-- 这里也是一样的,使用if标签判断 -->
        <if test="userId!=0">
            and user_id = #{userId}
        </if>
    </select>
</mapper>

编写对应测试方法

package com.zhiller.community.zhillercommunity;

import com.zhiller.community.zhillercommunity.dao.DiscussPostMapper;
import com.zhiller.community.zhillercommunity.dao.UserMapper;
import com.zhiller.community.zhillercommunity.entity.DiscussPost;
import com.zhiller.community.zhillercommunity.entity.User;
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.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = ZhillerCommunityApplication.class)
public class MapperTest {
    
    

    // 自动装配别忘了!!!
    @Autowired
    private DiscussPostMapper discussPostMapper;

    @Test
    public void testDiscussPost() {
    
    
        List<DiscussPost> discussPostList = discussPostMapper.selectDiscussPosts(0, 0, 10);
        for (DiscussPost discussPost : discussPostList) {
    
    
            System.out.println(discussPost);
        }

        int rows = discussPostMapper.selectDiscussPostRows(149);
        System.out.println(rows);
    }
}

DiscussPostService

对应的 Service 实际上很简单,就和我们的测试方法一样,也是两步走:

  1. autowired 自动装配 mapper
  2. 调用 mapper 中的方法实现查询

代码清单:/service/DiscussPostService.java

package com.zhiller.community.zhillercommunity.service;

import com.zhiller.community.zhillercommunity.dao.DiscussPostMapper;
import com.zhiller.community.zhillercommunity.entity.DiscussPost;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DiscussPostService {
    
    
    @Autowired
    private DiscussPostMapper discussPostMapper;

    public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit) {
    
    
        return discussPostMapper.selectDiscussPosts(userId, offset, limit);
    }

    public int findDiscussPostRows(int userId) {
    
    
        return discussPostMapper.selectDiscussPostRows(userId);
    }
}

Controller

定制了完善的 entity、service,接下来就是使用 controller 控制界面的显示了

由于我们是后端开发,无需理会前端如何,除非你是全栈;
前端部分由 thymeleaf 借助模板搞定,虽然没有做到前后端分离,但是也还是可以的;


代码清单:/controller/HomeController.java

@Controller
public class HomeController {
    
    
    @Autowired
    private DiscussPostService discussPostService;
    @Autowired
    private UserService userService;

    @GetMapping("/index")
    public String getIndexPage(Model model, Page page) {
    
    
        // 方法调用钱,SpringMVC会自动实例化Model和Page,并将Page注入Model.
        // 所以,在thymeleaf中可以直接访问Page对象中的数据.

        // 分页查询实体Page我们还没有定义,此刻会飘红,先不管
        page.setRows(discussPostService.findDiscussPostRows(0));
        page.setPath("/index");

        // 获取所有DiscussPost
        List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());

        // discussPosts将会被用作列表渲染
        List<Map<String, Object>> discussPosts = new ArrayList<>();
        if (list != null) {
    
    
            for (DiscussPost post : list) {
    
    
                Map<String, Object> map = new HashMap<>();
                // post和user为对应参数,后续html文件里面会用到
                map.put("post", post);
                User user = userService.findUserById(post.getUserId());
                map.put("user", user);
                discussPosts.add(map);
            }
        }

        // 向html模板提供一个可用参数,参数指向discussPosts
        model.addAttribute("discussPosts", discussPosts);
        return "/index";
    }
}

thymeleaf 模板参数介绍

下面是一些常见的模板介绍

th:each="map:${discussPosts}

  • th:each 类似于 v-for,列表渲染
  • map 为渲染子项的别名
  • discussPosts 为被渲染的项

th:src="${map.user.headerUrl}"
th:src 用于动态绑定图片地址
既然我们已经确定了子项别名,故可以直接通过该别名取出对应数据


th:utext="${map.post.title}
th:utext 是 Thymeleaf 的内联表达式,用于将表达式的结果作为不经过 HTML 转义的文本输出


th:if="${map.post.type==1}
可以执行判断操作,动态显示或隐藏部分标签


th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}"
可以内含指令,来改变输出格式


Page

开始制作分页查询模块

创建对应的 entity 类(注意!并不是说所有的 entity 都必须对应数据表,比如这个 page 就是为了索引用的)

代码清单:entity/page.java

package com.zhiller.community.zhillercommunity.entity;

public class Page {
    
    
    // 当前页码
    private int current = 1;
    // 显示上限
    private int limit = 10;
    // 数据总数(用于计算总页数)
    private int rows;
    // 查询路径(用于复用分页链接)
    private String path;

    public int getCurrent() {
    
    
        return current;
    }

    public void setCurrent(int current) {
    
    
        if (current >= 1) {
    
    
            this.current = current;
        }
    }

    public int getLimit() {
    
    
        return limit;
    }

    public void setLimit(int limit) {
    
    
        if (limit >= 1 && limit <= 100) {
    
    
            this.limit = limit;
        }
    }

    public int getRows() {
    
    
        return rows;
    }

    public void setRows(int rows) {
    
    
        if (rows >= 0) {
    
    
            this.rows = rows;
        }
    }

    public String getPath() {
    
    
        return path;
    }

    public void setPath(String path) {
    
    
        this.path = path;
    }

    /**
     * 获取当前页的起始行
     *
     * @return
     */
    public int getOffset() {
    
    
        // current * limit - limit
        return (current - 1) * limit;
    }

    /**
     * 获取总页数
     *
     * @return
     */
    public int getTotal() {
    
    
        // rows / limit [+1]
        if (rows % limit == 0) {
    
    
            return rows / limit;
        } else {
    
    
            return rows / limit + 1;
        }
    }

    /**
     * 获取起始页码
     *
     * @return int
     */
    public int getFrom() {
    
    
        int from = current - 2;
        return from < 1 ? 1 : from;
    }

    /**
     * 获取结束页码
     *
     * @return int
     */
    public int getTo() {
    
    
        int to = current + 2;
        int total = getTotal();
        return to > total ? total : to;
    }
}

对应的就是分页查询的 thymeleaf 模板代码

<!-- 分页 -->
<nav
	class="mt-5"
	th:if="${page.rows>0}"
>
	<ul class="pagination justify-content-center">
		<li class="page-item">
			<a
				class="page-link"
				th:href="@{${page.path}(current=1)}"
				>首页</a
			>
		</li>
		<li th:class="|page-item ${page.current==1?'disabled':''}|">
			<a
				class="page-link"
				th:href="@{${page.path}(current=${page.current-1})}"
				>上一页</a
			>
		</li>
		<li
			th:class="|page-item ${i==page.current?'active':''}|"
			th:each="i:${#numbers.sequence(page.from,page.to)}"
		>
			<a
				class="page-link"
				th:href="@{${page.path}(current=${i})}"
				th:text="${i}"
			></a>
		</li>
		<li th:class="|page-item ${page.current==page.total?'disabled':''}|">
			<a
				class="page-link"
				th:href="@{${page.path}(current=${page.current+1})}"
				>下一页</a
			>
		</li>
		<li class="page-item">
			<a
				class="page-link"
				th:href="@{${page.path}(current=${page.total})}"
				>末页</a
			>
		</li>
	</ul>
</nav>

猜你喜欢

转载自blog.csdn.net/delete_you/article/details/132648841