玩命加载中 . . .

springboot博客(一)(环境搭建)


springboot博客(一)(环境搭建)

前言

这是一篇关于springboot的个人博客后端搭建详细过程,关于前端页面的编写这里不再详细赘述,但后端用到的thymeleaf渲染页面和一些关键jquery也会记录在内。话不多说,我们现在就开始吧!

关于具体搭建过程请移步到环境搭建、页面处理

技术选型:

  • 后端:Spring Boot + JPA + thymeleaf模板
  • 数据库:MySQL
  • 前端UI:Semantic UI框架

工具与环境:

  • IDEA
  • Maven 3
  • JDK 8

需求与功能

1.1 用户故事

用户故事是敏捷框架中的一种开发方法。可以帮助开发者转换视角,以用户的角度更好的把握需求,从而实现具有商业价值的功能。

用户故事模板

  • 作为一个(某个角色) 使用者,我可以做(某个功能) 事情,如此可以有(某个商业价值) 的好处

关键点:角色、功能、商业价值

举例

  • 作为一个招聘网站注册用户,我想查看最近3天发布的招聘信息,以便于了解最新的招聘信息
  • 作为公司,可以张贴新工作。

个人博客系统的用户故事:

角色:普通访客管理员(我)

  • 访客,可以分页查看所有的博客
  • 访客,可以快速查看博客数最多的6个分类
  • 访客,可以查看所有的分类
  • 访客,可以查看某个分类下的博客列表
  • 访客,可以快速查看标记博客最多的10个标签
  • 访客,可以查看所有的标签
  • 访客,可以查看某个标签下的博客列表
  • 访客,可以根据年度时间线查看博客列表
  • 访客,可以快速查看最新的推荐博客
  • 访客,可以用关键字全局搜索博客
  • 访客,可以查看单个博客内容
  • 访客,可以对博客内容进行评论
  • 访客,可以赞赏博客内容
  • 访客,可以微信扫码阅读博客内容
  • 访客,可以在首页扫描公众号二维码关注我
  • 我,可以用户名和密码登录后台管理
  • 我,可以管理博客
    • 我,可以发布新博客
    • 我,可以对博客进行分类
    • 我,可以对博客打标签
    • 我,可以修改博客
    • 我,可以删除博客
    • 我,可以根据标题,分类,标签查询博客
  • 我,可以管理博客分类
    • 我,可以新增一个分类
    • 我,可以修改一个分类
    • 我,可以删除一个分类
    • 我,可以根据分类名称查询分类
  • 我,可以管理标签
    • 我,可以新增一个标签
    • 我,可以修改一个标签
    • 我,可以删除一个标签
    • 我,可以根据名称查询标签

1.2 功能规划


2、页面设计与开发

2.1 设计

页面规划:

前端展示:首页、详情页、分类、标签、归档、关于我

后台管理:登录页、后台首页、博客、分类、标签

2.2 页面开发

Semantic UI官网

Semantic UI中文官网

WebStorm下载与破解

背景图片资源

2.3 插件集成

编辑器 Markdown

内容排版 typo.css

动画 animate.css

代码高亮 prism

目录生成 Tocbot

滚动侦测 waypoints

平滑滚动 jquery.scrollTo

二维码生成 qrcode.js


环境搭建、页面处理

1. 环境构建

  1. 创建springboot工程,jdk8

  2. 引入模块

    1. web
    2. Thymeleaf
    3. JPA
    4. Mysql
    5. Aspects
    6. DevTools
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.5.7.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
            <!--这里thymeleaf使用3.x的版本-->
            <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
            <thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
    
            <dependency>
                <groupId>com.atlassian.commonmark</groupId>
                <artifactId>commonmark</artifactId>
                <version>0.10.0</version>
            </dependency>
    
            <dependency>
                <groupId>com.atlassian.commonmark</groupId>
                <artifactId>commonmark-ext-heading-anchor</artifactId>
                <version>0.10.0</version>
            </dependency>
            <dependency>
                <groupId>com.atlassian.commonmark</groupId>
                <artifactId>commonmark-ext-gfm-tables</artifactId>
                <version>0.10.0</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
  3. 配置文件application.yml

    spring:
      thymeleaf:
        mode: HTML
      profiles:
        active: dev
    

    application-dev.yml

    需要提前创建好数据库

    spring:
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/myblog?useUnicode=true&characterEncoding=utf-8
        username: root
        password: 1234
      jpa: #所有Jpa的配置项都在jpaProperties中
        hibernate:
          ddl-auto: update #更新或创建数据表结构
        show-sql: true #控制台显示sql
    logging:
      level:
        root: info
        top.jm: debug
      file: log/myblog-dev.log
    

    application-pro.xml

    spring:
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/myblog?useUnicode=true&characterEncoding=utf-8
        username: root
        password: 1234
      jpa:
        hibernate:
          ddl-auto: none
        show-sql: true
    logging:
      level:
        root: warn
        top.jm: info
      file: log/myblog-pro.log
    

    SpringBoot中有日志默认的生成以及切分,在这里我们可以重写SpringBoot默认日志配置,自定义日志大小和名称等等,在资源文件夹下添加logback-spring.xml进行配置

2. 异常处理

在页面访问的时候,会有一些比较常见的异常报错信息,比如路径无法访问404异常、服务器错误500异常以及自己定义的错误页面等等,SpringBoot框架提供了处理错误页面的方法,在这里,咱们对404、500、error异常页面进行处理。

  1. resources/templates下创建error文件夹,创建404/500/error.html,springboot在出现该状态码时会自动在该目录找该页面

  2. templates文件夹下创建index.html,用于测试

  3. 创建controller,跳转测试用

    @Controller
    public class ExceptionController {
        @GetMapping("/")
        public String indexController(){
            //int i = 1/0;
            return "index";
        }
    }
    

    小知识:

    因为导入了devtools的jar包,我们可以实现热部署,重编译运行就可以了

跳转error.html的方法

对于404和500错误页面,SpringBoot可以根据页面的命名方式找到对应的文件,而自定义的错误就需要我们自己来拦截了,让代码出现问题的时候跳转到我们自己定义的错误页面,这里就需要自定义拦截器。

  1. 创建拦截器,拦截所有controller请求

    @ControllerAdvice//表示拦截controller的所有请求
    public class ControllerExceptionHandler {
        private final Logger logger =
                LoggerFactory.getLogger(ControllerExceptionHandler.class);//创建日志对象
        @ExceptionHandler({Exception.class})//表示这是个异常处理的拦截器
        public ModelAndView exceptionHandler(HttpServletRequest request,Exception e){
            logger.error("Request URL:{},Exception:{}",request.getRequestURL(),e);
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("url",request.getRequestURL());
            modelAndView.addObject("exception",e);
            modelAndView.setViewName("error/error");//这样路径会识别到templates下的路径
            return modelAndView;
        }
    }
    

错误页面信息显示(便于开发时在页面看到错误)

  1. 在error.html中加上
<div>
 <div th:utext="'&lt;!--'" th:remove="tag"></div><!--这里是转义,即“<--”注释的开头,这样在界面就不会显示,开发人员开启控制台的原代码就能看到异常信息-->
 <div th:utext="'Failed Request URL : ' + ${url}" th:remove="tag">
</div>
 <div th:utext="'Exception message : ' + ${exception.message}"
th:remove="tag"></div>
 <ul th:remove="tag">
 <li th:each="st : ${exception.stackTrace}" th:remove="tag"><span
th:utext="${st}" th:remove="tag"></span></li>
 </ul>
 <div th:utext="'--&gt;'" th:remove="tag"></div>
</div>

让404仍然跳转到404.html,而不是所有异常都跳转error.html

  1. 修改异常(测试用)

    @GetMapping("/")
    public String indexController(){
        //int i = 1/0;
        String blog = null;
        if(blog == null){
            throw new NotFindException("blog是null");
        }
        return "index";
    }
    
  2. 创建异常类在myblog包内

    @ResponseStatus(HttpStatus.NOT_FOUND)//表示这个状态码是404
    public class NotFindException extends RuntimeException {
    
        public NotFindException(String message) {
            super(message);
        }
    
        public NotFindException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public NotFindException() {
        }
    }
    
  3. 自定义拦截器中增加

    //如果异常信息是404,就抛出去给springboot来处理,自己的拦截器不拦截
    if(AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class)!=null){
        throw e;
    }
    

3. 日志处理

效果

每发送一个请求,控制台就打印日志

2020-11-30 10:50:25.544  INFO 16016 --- [nio-8080-exec-2] top.jm.myblog.aspect.LogAspect           : request:{}RequestLog{url='http://localhost:8080/', ip='0:0:0:0:0:0:0:1', classMethod='top.jm.myblog.controller.IndexController.indexController', args=[Page request [number: 0, size 5, sort: id: DESC], {}]}

步骤

​ 采用spring的aop来实现日志处理,AOP可以以切面的形式拦截,将日志内容记录下来,这里记录以下日志信息:

  • 访问的URL
  • 访问者的IP
  • 访问时调用的方法
  • 访问时传递的参数
  • 访问时返回的内容
  1. 创建一个切面类

    @Aspect
    @Component
    public class LogAspect {
        //获取日志对象
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        //切面
        @Pointcut("execution(* top.jm.myblog.controller.*.*(..))")
        public void log(){
        }
        //前置方法
        @Before("log()")
        public void doBefore(JoinPoint joinPoint){
            //获取request对象
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            //获取信息
            String url = request.getRequestURL().toString();//获取url
            String ip = request.getRemoteAddr();//获取ip
            String method = joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName();//获取方法名
            Object[] args = joinPoint.getArgs();//获取请求参数
            RequestLog requestLog = new RequestLog(url,ip,method,args);//封装并用日志输出
            logger.info("request:{}"+requestLog);
            //logger.info("--------before--------");
        }
        //后置方法
        @After("log()")
        public void doAfter(){
            //logger.info("--------after----------");
        }
        //返回值方法
        @AfterReturning(pointcut = "log()",returning = "result")
        public void doAfterReturning(Object result){
           // logger.info("return-----{}",result);
        }
        //用于封装log信息
        private class RequestLog{
            private String url;
            private String ip;
            private String classMethod;
            private Object[] args;
    
            public RequestLog(String url, String ip, String classMethod, Object[] args) {
                this.url = url;
                this.ip = ip;
                this.classMethod = classMethod;
                this.args = args;
            }
    
            @Override
            public String toString() {
                return "RequestLog{" +
                        "url='" + url + '\'' +
                        ", ip='" + ip + '\'' +
                        ", classMethod='" + classMethod + '\'' +
                        ", args=" + Arrays.toString(args) +
                        '}';
            }
        }
    }
    
  2. 修改controller(测试用)

        @GetMapping("/{id}/{name}")
        public String indexController(@PathVariable("id") int id,@PathVariable("name") String name){
            logger.info("-------index--------");
            return "index";
        }
    

4. 页面处理

  1. 静态页面导入project

    1. 将templates、static导入进去
    2. 使用maven的clean清除target缓存
    3. 重新运行通过controller访问Index.html
    4. 发现有些样式和图片没有引进,需要修改thymeleaf路径
      1. th:href="@{/css/me.css}"
      2. th:src="@{images/wechat.jpg}"
  2. thymeleaf公共布局抽取

    作用:将公共布局抽取到一个页面中,修改公共页面,即修改所有样式

    模板:th:fragment=""

    • 传参:th:fragment="模板名(参数名)"
    • 使用参数:th:replace="${参数名}"

    引用:th:replace="模板所在的html名::模板名"(将表标签内及自己全部替换为模板)

    • 传参:th:replace="模板所在的html名::模板名(参数)"

    • 传参的过程:

      1. 引用的参数传给模板
      2. 模板进行接收参数进行渲染
      3. 渲染好的模板返回给引用
    • 参数格式:

      ~{ templatename :: selector }
      

      支持:
      ~{ templatename :: #html_id } 表示 取 html 的 id 标签
      ~{ ::selector} 表示 代码段在本页面
      ~{ templatename } 引入 templatename 所有的 html 代码

    1. 建立公共页面_fragment.html

    2. 抽取和使用fragment

      1. head的抽取

        <head th:fragment="head(title)">
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title th:replace="${title}">博客详情</title>
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css">
            <link rel="stylesheet" href="../static/css/typo.css" th:href="@{/css/typo.css}">
            <link rel="stylesheet" href="../static/css/animate.css" th:href="@{/css/animate.css}">
            <link rel="stylesheet" href="../static/lib/prism/prism.css" th:href="@{lib/prism/prism.css}">
            <link rel="stylesheet" href="../static/lib/tocbot/tocbot.css" th:href="@{/lib/tocbot/tocbot.css}">
            <link rel="stylesheet" href="../static/css/me.css" th:href="@{/css/me.css}">
        </head>
        
        <head th:replace="_fragment::head(~{::title})"></head>
        

        分析:

        1. 模板抽取,参数为title
        2. 引用时,传入参数~{::title},表示引用的title标签作为参数传入模板
        3. 模板的th:replace="${title}"渲染上值
        4. 渲染后的模板替换到引用的位置
      2. nav导航的抽取

        <nav class="ui inverted attached segment m-padded-tb-mini m-shadow-small" th:fragment="nav(n)">
            <div class="ui container">
                <div class="ui inverted secondary stackable menu">
                    <h2 class="ui teal header item">Blog</h2>
                    <a href="#" class="m-item item m-mobile-hide" th:classappend="${n==1}?'active':''"><i class="mini home icon"></i>首页</a>
                    <a href="#" class="m-item item m-mobile-hide" th:classappend="${n==2}?'active':''"><i class="mini idea icon"></i>分类</a>
                    <a href="#" class="m-item item m-mobile-hide" th:classappend="${n==3}?'active':''"><i class="mini tags icon"></i>标签</a>
                    <a href="#" class="m-item item m-mobile-hide" th:classappend="${n==4}?'active':''"><i class="mini clone icon"></i>归档</a>
                    <a href="#" class="m-item item m-mobile-hide" th:classappend="${n==5}?'active':''"><i class="mini info icon"></i>关于我</a>
                    <div class="right m-item item m-mobile-hide">
                        <div class="ui icon inverted transparent input m-margin-tb-tiny">
                            <input type="text" placeholder="Search....">
                            <i class="search link icon"></i>
                        </div>
                    </div>
             </div>
            </div>
            <a href="#" class="ui menu toggle black icon button m-right-top m-mobile-show">
                <i class="sidebar icon"></i>
            </a>
        </nav>
        
        <nav th:replace="_fragment::nav(1)" class="ui inverted attached segment m-padded-tb-mini m-shadow-small" >
          </nav>
        
    3. 底部footer的抽取

      <footer th:fragment="footer" class="ui inverted vertical segment m-padded-tb-massive">
          ...
      </footer>
      
        <footer th:replace="_fragment::footer" class="ui inverted vertical segment m-padded-tb-massive">
        </footer>
      
      1. script抽取

        <th:block th:fragment="script">
        <script src="https://cdn.jsdelivr.net/npm/jquery@3.2/dist/jquery.min.js"></script>
        <script src="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js"></script>
        <script src="//cdn.jsdelivr.net/npm/jquery.scrollto@2.1.2/jquery.scrollTo.min.js"></script>
        
        <script src="../static/lib/prism/prism.js" th:src="@{lib/prism/prism.js}"></script>
        <script src="../static/lib/tocbot/tocbot.min.js" th:src="@{lib/tocbot/tocbot.min.js}"></script>
        <script src="../static/lib/qrcode/qrcode.min.js" th:src="@{lib/qrcode/qrcode.min.js}"></script>
        <script src="../static/lib/waypoints/jquery.waypoints.min.js" th:src="@{lib/waypoints/jquery.waypoints.min.js}"></script>
        </th:block>
        
        <!--/*/<th:block th:fragment="script">/*/-->
        <script src="https://cdn.jsdelivr.net/npm/jquery@3.2/dist/jquery.min.js"></script>
        <script src="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js"></script>
        <!--/*/</th:block>/*/-->
        

        这里的/*/:在html中是注释的存在,但是在thymeleaf模板中,仍然有效

  3. 错误页面美化

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
    <head th:replace="_fragment::head(~{::title})">
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>首页</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css">
        <link rel="stylesheet" href="../static/css/me.css" th:href="@{/css/me.css}">
    </head>
    <body>
    
    <!--导航-->
    <nav th:replace="_fragment::nav(0)" class="ui inverted attached segment m-padded-tb-mini m-shadow-small" >
        <div class="ui container">
            <div class="ui inverted secondary stackable menu">
                <h2 class="ui teal header item">Blog</h2>
                <a href="#" class="active m-item item m-mobile-hide"><i class="mini home icon"></i>首页</a>
                <a href="#" class="m-item item m-mobile-hide"><i class="mini idea icon"></i>分类</a>
                <a href="#" class="m-item item m-mobile-hide"><i class="mini tags icon"></i>标签</a>
                <a href="#" class="m-item item m-mobile-hide"><i class="mini clone icon"></i>归档</a>
                <a href="#" class="m-item item m-mobile-hide"><i class="mini info icon"></i>关于我</a>
                <div class="right m-item item m-mobile-hide">
                    <div class="ui icon inverted transparent input m-margin-tb-tiny">
                        <input type="text" placeholder="Search....">
                        <i class="search link icon"></i>
                    </div>
                </div>
            </div>
        </div>
        <a href="#" class="ui menu toggle black icon button m-right-top m-mobile-show">
            <i class="sidebar icon"></i>
        </a>
    </nav>
    <br>
    <br>
    <br>
    <div class="m-container-small m-padded-tb-massive">
        <div class="ui error message m-padded-tb-huge">
            <div class="ui container"></div>
            <h1>404</h1>
            <p>对不起,您访问的资源不存在!</p>
        </div>
    </div>
    <br>
    <br>
    <br>
    
    <!--底部footer-->
    <footer th:replace="_fragment::footer" class="ui inverted vertical segment m-padded-tb-massive">
        <div class="ui center aligned container">
            <div class="ui inverted divided stackable grid">
                <div class="three wide column">
                    <div class="ui inverted link list">
                        <div class="item">
                            <img src="../static/images/wechat.jpg" th:src="@{images/wechat.jpg}" class="ui rounded image" alt="" style="width: 110px">
                        </div>
                    </div>
                </div>
                <div class="three wide column">
                    <h4 class="ui inverted header m-text-thin m-text-spaced " >最新博客</h4>
                    <div class="ui inverted link list">
                        <a href="#" class="item m-text-thin">用户故事(User Story)</a>
                        <a href="#" class="item m-text-thin">用户故事(User Story)</a>
                        <a href="#" class="item m-text-thin">用户故事(User Story)</a>
                    </div>
                </div>
                <div class="three wide column">
                    <h4 class="ui inverted header m-text-thin m-text-spaced ">联系我</h4>
                    <div class="ui inverted link list">
                        <a href="#" class="item m-text-thin">Email:lirenmi@163.com</a>
                        <a href="#" class="item m-text-thin">QQ:865729312</a>
                    </div>
                </div>
                <div class="seven wide column">
                    <h4 class="ui inverted header m-text-thin m-text-spaced ">Blog</h4>
                    <p class="m-text-thin m-text-spaced m-opacity-mini">这是我的个人博客、会分享关于编程、写作、思考相关的任何内容,希望可以给来到这儿的人有所帮助...</p>
                </div>
            </div>
            <div class="ui inverted section divider"></div>
            <p class="m-text-thin m-text-spaced m-opacity-tiny">Copyright © 2016 - 2017 Lirenmi Designed by Lirenmi</p>
        </div>
    
    </footer>
    </body>
    </html>
    

功能实现

1. 设计与规范

我们根据面向对象编程的思想,先建立实体类,利用JPA根据实体类生成对应的数据库。

1.1 实体类

  • 博客Blog
  • 博客分类Type
  • 博客标签Tag
  • 博客评论Comment
  • 用户User

实体关系

评论类自关联关系

Blog类

Type类

Tag类

Comment类

User类

1.2 应用分层

1.3 命名约定

Service/Dao层命名约定:

  • 获取单个对象的方法用get作前缀
  • 获取多个对象的方法用list作前缀
  • 获取统计值的方法用count作前缀
  • 插入的方法save或insert作前缀
  • 删除的方法用remove或delete作前缀
  • 修改的方法用update作前缀

2. 实体类构建

  1. 创建实体类

  2. 配置实体关系

    1. 声明对象变量

    2. 指明一对多/多对多关系,并表示“多”为维护方

      注意几点:

      1. @Table别忘了加name,虽然爆红,但不影响使用
      2. @GeneratedValue指明id生成策略
      3. Date需要指明@Temporal时间戳
      4. @ManyToOne:多对1,1对多的关系界定:当前实体类和对方的关系。并且1的一方需要指明被谁维护mappedBy
      5. 记得加上引用对象的get/set方法
      6. @ManyToMany级联新增
      @Entity//指明实体类
      @Table(name = "t_blog")//指明映射哪个表
      public class Blog {
      
          @Id//标记主键
          @GeneratedValue//主键的生成策略,默认自增
          private Long id;
          private String title; //标题
          private String content;//内容
          private String firstPicture;//首图
          private String flag;//标记
          private Integer views;//浏览次数
          private boolean appreciation;//赞赏开启
          private boolean shareStatement;//版权开启
          private boolean commentabled;//评论开启
          private boolean published;//发布
          @Temporal(TemporalType.TIMESTAMP)//表示是一个完整的时间
          private Date createTime;//创建时间
          @Temporal(TemporalType.TIMESTAMP)
          private Date updateTime;//更新时间
      
          @ManyToOne//多个博客属于1个类型
          private Type type;
          @ManyToMany(cascade = {CascadeType.PERSIST})//级联新增,当新增一个tag,数据库也会跟着新增
          private List<Tag> tags = new ArrayList<>();//list的需要new
          @ManyToOne
          private User user;
          @OneToMany(mappedBy = "blog")//“1”的一方需要被“多”的维护:mappedBy指:被维护
          private List<Comment> comments = new ArrayList<>();
      
  3. 启动项目,自动生成表


文章作者: 小苏
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小苏 !
评论
  目录