在前面的两篇文章中,分别讲解了Spring的IOC容器原理,以及如何从零开始创建一个Spring容器。但是实际工作中,光有这些肯定是不够的,还需要在这个基础上再扩展数据库、Redis缓存、消息队列等。所以接下来就一步步的从无到有,扩展这个基本的Spring容器。
首先我们知道,一般情况下,我们会把业务逻辑分为三层,既Controller,Service,Dao。
从引入Maven依赖开始,从 官网 中获取SpringBoot的Maven依赖,接着引入数据库,先使用最简单的H2数据库作为例子。引入H2数据库的 Maven依赖 之后,就可以开始着手为Spring配置数据库了,在main目录下新建resources文件夹,在其中新建application.properties文件,写入基本配置信息:
spring.datasource.url=jdbc:h2:file:C:/Users/catsme/Desktop/my-first-spring spring.datasource.username=root spring.datasource.password=password spring.datasource.driver-class-name=org.h2.Driver 复制代码
设计数据库:
测试用数据:
User | Id | Name |
---|---|---|
User_1 | 1 | 大娃 |
User_2 | 2 | 二娃 |
User_3 | 3 | 三娃 |
User_4 | 4 | 四娃 |
User_5 | 5 | 五娃 |
User_6 | 6 | 六娃 |
Score | Id | User_id | Score |
---|---|---|---|
Score_1 | 1 | 1 | 90 |
Score_2 | 2 | 2 | 91 |
Score_3 | 3 | 3 | 89 |
Score_4 | 4 | 4 | 88 |
Score_5 | 5 | 5 | 92 |
Score_6 | 6 | 6 | 94 |
Socre_7 | 7 | 2 | 2 |
Socre_8 | 8 | 4 | 3 |
在resources目录下,新建db/migration/V1__CreateTables.sql文件,写入sql语句:
--用户表 create table user(name varchar(200), id bigint primary key auto_increment); --成绩表 create table score( id bigint primary key auto_increment, user_id bigint, Score bigint); insert into user(name,id) values ('大娃',1),('二娃',2),('三娃',3),('四娃',4),('五娃',5),('六娃',6); insert into score(id,user_id,score) values (1,1,90),(2,2,91),(3,3,89),(4,4,88),(5,5,92),(6,6,94),(7,2,2),(8,4,3); 复制代码
添加以下信息至pom.xml中,用以引入flyway插件:
<plugin> <groupId>org.flywaydb</groupId> <artifactId>flyway-maven-plugin</artifactId> <version>6.0.6</version> <configuration> <url>jdbc:h2:file:C:/Users/catsme/Desktop/my-first-spring</url> <user>root</user> <password>password</password> </configuration> </plugin> 复制代码
接着,使用 mvn flyway:migrate
命令初始化数据库,接下来就是引用MyBatis进行复杂的sql操作了。
(一) 使用注解的方式
首先声明一个接口:
@Mapper public interface MyMapper { @Select("select * from user where id = #{}") User getUser(@Param("id") Integer id); } 复制代码
接着在mybatis目录下的config.xml文件中的mapper块中声明该接口:
<mapper class="hello.dao.MyMapper"/>
。到现在可以发现,Spring关联了MyBatis,MyBatis关联了接口,那么问题来了,接下来要怎么实现这个MyMapper呢?
通过注解声明他们之间的依赖关系,通过依赖注入实现这层关系。代码如下:
//使用Autowired注解或者Resource注解注入依赖关系 @Autowired private UserMapper myMapper; @RequestMapping("/getUser") @ResponseBody public User getUser(){ return myMapper.getUserById(3); } 复制代码
那么就可以在浏览器中通过访问这个接口,实现获取数据库中的数据。
现在有这样一个需求:按总成绩的降序,对用户进行排序,这样的话,就不好使用上面这种通过接口去实现的方式。 在db/mybatis目录下新建config.xml文件,写入基本配置信息:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="logImpl" value="LOG4J"/> </settings> <typeAliases> <typeAlias alias="User" type="hello.entity.User"/> <typeAlias alias="ScoreItem" type="hello.entity.ScoreItem"/> </typeAliases> <mappers> <mapper resource="db/mybatis/MyMapper.xml"/> <mapper class="hello.dao.UserMapper"/> </mappers> </configuration> 复制代码
注意:由于已经在application.properties文件中已经绑定了数据库基本信息,所以这时候已经不需要在xml文件中配置数据源了。直接着在Spring中写入MyBatis的声明,代码如下:
mybatis.config-location=classpath:db/mybatis/config.xml
接在在MyBatis的映射文件中写好sql语句,代码如下:
<?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="MyUserMapper"> <select id="rankUser" resultMap="scoreItem"> select t.user_id,t.score_sum,user.name as user_name from (select user_id,sum(score) as score_sum from score group by user_id)t inner join user on user.id = t.user_id order by t.score_sum desc </select> <!-- 非常复杂的结果映射 --> <resultMap id="scoreItem" type="ScoreItem"> <result property="score" column="score_sum"/> <association property="user" javaType="User"> <result property="name" column="user_name"/> <result property="id" column="user_id"/> </association> </resultMap> </mapper> 复制代码
到这里,该配置好的信息基本都以配置完毕,接下来该进行的就是,通过分层的思想,在不同的逻辑层上实现对应的功能。 对于dao层,实现与数据库的交互:
@Service public class RankDao { @Autowired SqlSession sqlSession; public List<ScoreItem> getRankItem(){ return sqlSession.selectList("MyUserMapper.rankUser"); } } 复制代码
在这里可以看到,原本需要使用SqlSessionFactory获取sqlSession,现在只需要在Spring中声明Autowired然后直接创建实例即可调用sqlSession的方法。接下来在entiy层中创建实体对象,用以存储从dao层拿到的数据,代码:
public class ScoreItem { private Integer id; private Integer score; private User user; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } public Integer getScore() { return score; } public void setScore(Integer score) { this.score = score; } } 复制代码
在service层实现业务逻辑:
@Service public class RankService { @Autowired private RankDao rankDao; public List<ScoreItem> sort() { return rankDao.getRankItem(); } } 复制代码
最后在Controller层实现与用户界面的交互:
@RestController public class HelloController { @Autowired private RankService rankService; @RequestMapping("/getUser") @ResponseBody public List<ScoreItem> getUserFromDatabase() { return rankService.sort(); } } 复制代码
最后结果如下:
可以看到,这样就从头到尾实现了一个较小的业务逻辑,如果加上一些渲染的话,就能变成一个真正的页面。
从数据库中拿出数据后,接下来要做的就是对页面进行渲染了,这个过程前端跟后端都可以实现。
何谓 模板引擎 呢,源自 百度百科 的解释:模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。在这里使用FreeMarker工具渲染页面。
何谓 FreeMarker 呢,源自 维基百科 的解释: FreeMarker是一个基于Java的模板引擎,最初专注于使用MVC软件架构生成动态网页。但是,它是一个通用的模板引擎,不依赖于servlets或HTTP或HTML,因此它通常用于生成源代码,配置文件或电子邮件。
所以我们可以通过FreeMarker来生成HTML页面。引入FreeMarker的 mavne依赖 ,根据官网提示:
resources ├── application.yml ├── data-h2.sql ├── schema-h2.sql ├── static │ └── css │ └── style.css └── templates ├── index.ftl └── showCities.ftl 复制代码
我们需要在resources目录下创建一个templates文件夹,这样FreeMarker默认会去templates文件夹中寻找。然后新建index.ftl文件,在.ftl模板文件中写好除初始格式,然后把内容传入其中。.ftl文件中写入HTML格式的代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>成绩排行榜</title> </head> <body> <table> <thead> <tr> <th>用户号码</th> <th>成绩</th> <th>用户名</th> </tr> </thead> <tbody> <tr> <td>${index}</td> <td>${name}</td> <td>${score}</td> </tr> </tbody> </table> </body> </html> 复制代码
在这里定义好渲染的HTML文档的模板之后,就是想办法将数据传入其中了。与MyBatis中类似,代码如下:
@RequestMapping("/getUser") @ResponseBody public ModelAndView getUserFromDatabase() { Map<String, Object> model = new HashMap<>(); model.put("index",1); model.put("name","zhangsan"); model.put("score",97); //接受两个参数,一个模板文件一个数据 return new ModelAndView("index",model); } 复制代码
结果:
可以看到这时候,我们就通过向模板文件中传入参数,实现了对页面的渲染。这只是测试一下效果,实际上我们是需要通过循环将内容展现出来。那么接下来就是把数据库中真正的数据拿出来,放在页面中了。问题来了:从数据库中拿到的是六条数据,那么实现循环渲染呢?查阅 FreeMarker官网后,修改模板文件中的部分代码:
<thead> <tr> <th>序号</th> <th>成绩</th> <th>姓名</th> <th>学号</th> </tr> </thead> <tbody> <#list items as item> <tr> <td>${item?index+1}</td> <td>${item.user.name}</td> <td>${item.score}</td> <td>${item.user.id}</td> </tr> </#list> </tbody 复制代码
实现效果:
这里原本是可以直接使用前端的框架进行JS的设计的,但是为了加深对前后端渲染的理解(实际上是自己不会),所以使用最原始的方式进行,工作中千万不要用!!!
在resources目录下新建static文件夹,在这个文件夹中的内容可以被直接访问。在其中新建index.html页面文档,在文档中写入:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>成绩排行榜</title> </head> <body> <table id="rank-table"> <thead> <tr> <th>序号</th> <th>成绩</th> <th>姓名</th> <th>学号</th> </tr> </thead> <tbody> </tbody> </table> </body> </html> 复制代码
这时候是可以通过浏览器直接访问index.html的:
接下来在html中添加script
标签,用以发送http请求:
<script> var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if( xhr.readyState == 4){ if( xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ var json = xhr.responseText; <!--后将获取的结果打印至控制台--> console.log(json); } } }; <!--先通过getRankItem接口发送请求--> xhr.open("get", "/getRankItem", true); xhr.send(null); </script> 复制代码
接下来就可以在浏览器中看到获取的JSON字符串:
现在需要做的是,通过javascript将script标签中获取的结果,拼接到html中,展示到页面上。注意:现实工作中不要这样做!现学现卖,使用最基础的方式实现字符串的拼接,然后将其写入html中:
<script> var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if( xhr.readyState == 4){ if( xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ var json = xhr.responseText; var list = JSON.parse(json); var str1 = ''; var step; for (step = 0; step < 5; step++) { str1 += '<tr><td>'+(step+1)+'</td><td>'+list[step].score+'</td><td>'+list[step].user.name+'</td><td>'+list[step].user.id+'</td></tr> ' } var str = '<tr><th>序号</th><th>成绩</th><th>姓名</th><th>学号</th>'+str1+ '<tr><td>6</td><td>'+list[5].score+'</td><td>'+list[5].user.name+'</td><td>'+list[5].user.id+'</td></tr>' document.getElementById('rank-table').innerHTML = str; } } }; xhr.open("get", "/getRankItem", true); xhr.send(null); </script> 复制代码
实现效果如下:
mvn flyway:migrate
命令报错: -> Applied to database : 1062144176 -> Resolved locally : 1432425380 复制代码
这种情况需要删除数据库中的记录:
File does not end with a newline.'
错误 删除.circle目录下的
<module name="NewlineAtEndOfFile"> <property name="lineSeparator" value="lf" /> </module> 复制代码
即可。
项目地址: github.com/Scott-YuYan…