springboot整合持久层
持久层是javaEE中访问数据库的核心操作,springboot中对常见的持久层框架都提供了自动化配置,例如jdbcTemplate、JPA等,MyBatis的自动化配置则是MyBatis官方提供的。
- 整合jdbcTemplate:
jdbcTempalte是springboot提供的一套JDBC模版框架,利用AOP技术来解决直接使用JDBC时大量重复代码的问题。JdbcTemplate虽然没有MyBatis那么灵活,单=但是比直接使用JDBC要方便很多。Springboot中对JdbcTemplate的使用提供了自动化配置类JdbcTemplateAutoConfiguration。具体dao层代码如下:
在执行查询操作中,需要有一个RowMapper将查询出来的列和实体类中的属性一一对应起来。如果列名和属性名都是相同的,那么可以直接使用BeanPropertyRowMaper;如果不同,就需要开发者自己实现RowMapper接口。 - 整合MyBatis:
MyBatis几乎避免了所有的JDBC代码手动设置参数以及获取结果集。在传统的SSM框架整合中,使用MyBatis需要带量的xml配置,而在springboot中,MyBatis官方提供了一套自动化配置方案,可以做到MyBatis开箱即用。
创建Mapper类
@Mapper
public interface BookMapper {
int addBook(Book book);
int deleteBookById(Integer id);
int updateBookById(Book book);
Book getBookById(Integer id);
List getAllBooks();
}
在相同位置创建mapper.xml
```java
<?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="org.sang.mapper.BookMapper">
<insert id="addBook" parameterType="org.sang.model.Book">
INSERT INTO book(name,author) VALUES (#{name},#{author})
</insert>
<delete id="deleteBookById" parameterType="int">
DELETE FROM book WHERE id=#{id}
</delete>
<update id="updateBookById" parameterType="org.sang.model.Book">
UPDATE book set name=#{name},author=#{author} WHERE id=#{id}
</update>
<select id="getBookById" parameterType="int" resultType="org.sang.model.Book">
SELECT * FROM book WHERE id=#{id}
</select>
<select id="getAllBooks" resultType="org.sang.model.Book">
SELECT * FROM book
</select>
</mapper>
针对mapper接口中的每一个方法都在mapper.xml中列出实现。在maven工程中,xml配置文件建议写在resource目录下,但是上文的mapper.xml写在了包下,Maven运行时会忽略,因此需要在pom.xml中重新指明资源文件位置。
- 整合Spring Data JPA:
作为一名java EE工程师,基本都有听说过Hibernate框架,Hibernate是一个ORM框架,而JPA则是一种ORM规范,JPA和Hibernate的关系就像JDBC与JDBC驱动的关系,即JPA制订了ORM规范,而Hibernate是这些规范的实现(事实上,是先有HIbernate后有JPA,JPA规范的起草者也是HIbernate的作者),因此从功能上讲,Jpa是HIbernate的一个子集。jpa我们平时用起来最简单,所以使用频率比较高,我在这只简单写一点代码展示。
public interface BookDao extends JpaRepository<Book,Integer>{
List<Book> getBooksByAuthorStartingWith(String author);
List<Book> getBooksByPriceGreaterThan(Float price);
@Query(value = "select * from t_book where id=(select max(id) from t_book)",nativeQuery = true)
Book getMaxIdBook();
@Query("select b from t_book b where b.id>:id and b.author=:author")
List<Book> getBookByIdAndAuthor(@Param("author") String author, @Param("id") Integer id);
@Query("select b from t_book b where b.id<?2 and b.name like %?1%")
List<Book> getBooksByIdAndName(String name, Integer id);
}
既定的方法命名规则不一定满足所有开发需求,因此JPA也支持自定义JPQL或者原生SQL。
构建RESTful服务
REST是一种Web软件架构风格,它是一种风格,而不是标准,匹配或兼容这种架构风格的网络服务成为REST服务。REST服务简洁并且有层次,REST通常基于HTTP,URI和XML以及HTML这些现有的广泛流行的协议和标准。在REST中,资源是由URI来制定的,对资源的增删改查操作可以通过HTTP协议提供的GET、POST、PUT、DELETE等方法来实现。使用REST可以更高效地利用缓存来提高响应速度,同时REST中的通信回话状态由客户端来维护,这可以让不同的服务器处理一系列请求中的不同请求,进而提高服务器的扩展性。在前后端分离的项目中,一个设计良好的Web软件架构必然要满足REST风格。
Spring Boot 缓存
spring.31中开始对缓存提供支持,核心思路是对方法的缓存,当开发者调用一个方法时,将方法的参数和返回值作为key/value缓存起来,当再次调用该方法时,如果缓存中有数据,就直接从缓存中获取,否则再去执行该方法。但是,spring并未提供缓存的实现,而是提供了一套缓存api,开发者可以自由选择缓存的实现,目前springboot支持的缓存有如下几种:
<JCache、EhCache、Hazelcast、Infinispan、Couchbase、Redis、Caffenine、Simple>
Ehcache
Ecache缓存在java开发领域已是盛名已久,在springboot下只需要一个配置文件就可以将Ehcache集成到项目中。Ehcache 2.x的使用大致步骤。
<ehcache>
<diskStore path="java.io.tmpdir/cache"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<cache name="book_cache"
maxElementsInMemory="10000"
eternal="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="10"/>
</ehcache>
创建resources目录下的ehcache.xml文件作为Ehcache缓存的配置文件。
再在项目的入口类上加@EnableCaching
@Service
@CacheConfig(cacheNames = "book_cache")
public class BookDao {
@Autowired
MyKeyGenerator myKeyGenerator;
@Cacheable(keyGenerator = "myKeyGenerator")
public Book getBookById(Integer id) {
System.out.println("getBookById");
Book book = new Book();
book.setId(id);
book.setName("三国演义");
book.setAuthor("罗贯中");
return book;
}
@CachePut(key = "#book.id")
public Book updateBookById(Book book) {
System.out.println("updateBookById");
book.setName("三国演义2");
return book;
}
@CacheEvict(key = "#id")
public void deleteBookById(Integer id) {
System.out.println("deleteBookById");
}
}
创建BOOKDAO
如果开发者觉得这些key不能满足开发需求,开发者可以自定义缓存key的生成器KeyGenerator。
spring Boot安全管理
一般项目都会有严格的认证和授权操作,在java开发领域常见的安全框架有Shiro和Spring Security。Shiro是一个轻量级的安全管理框架,提供了认证、授权、会话管理、密码管理、缓存管理等功能。spring security是一个相对复杂的安全管理框架,功能比Shiro更加强大,权限控制颗粒度更高,对OAuth2的支持也更友好,又因为Spring Security源自spring家族,因此可以和spring框架无缝整合,特别是Spring Boot中提供的自动化配置方案,可以让Spring Security的使用更加便捷。
spring security在项目开发中用的比较多,在这不做展示,主要展示一下我以前没接触过的Shiro安全框架的用法。
Shiro整合
在传统的ssm框架中,手动整合Shiro的配置步骤还是比较多的,针对springboot,shiro官方提供了Shiro-spring-boot-web-starter用来简化shiro在Spring Boot的配置。
shiro.enabled=true
shiro.web.enabled=true
shiro.loginUrl=/login
shiro.successUrl=/index
shiro.unauthorizedUrl=/unauthorized
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
shiro.sessionManager.sessionIdCookieEnabled=true
shiro的配置
接下来再配置两个最基本的Bean
@Configuration
public class ShiroConfig {
@Bean
public Realm realm() {
TextConfigurationRealm realm = new TextConfigurationRealm();
realm.setUserDefinitions("sang=123,user\n admin=123,admin");
realm.setRoleDefinitions("admin=read,write\n user=read");
return realm;
}
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition =
new DefaultShiroFilterChainDefinition();
chainDefinition.addPathDefinition("/login", "anon");
chainDefinition.addPathDefinition("/doLogin", "anon");
chainDefinition.addPathDefinition("/logout", "logout");
chainDefinition.addPathDefinition("/**", "authc");
return chainDefinition;
}
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
最后一个shiroDialect这个如果不使用tymeleaf模板的话就可以不用加这个。
@Controller
public class UserController {
@PostMapping("/doLogin")
public String doLogin(String username, String password, Model model) {
UsernamePasswordToken token =
new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (AuthenticationException e) {
model.addAttribute("error", "用户名或密码输入错误!");
return "login";
}
return "redirect:/index";
}
@RequiresRoles("admin")
@GetMapping("/admin")
public String admin() {
return "admin";
}
@RequiresRoles(value = {"admin","user"},logical = Logical.OR)
@GetMapping("/user")
public String user() {
return "user";
}
}
配置好controller
对于其他不需要角色就能访问的接口,直接在WebMvc中配置即可。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/index").setViewName("index");
registry.addViewController("/unauthorized").setViewName("unauthorized");
}
}
可以创建个全局异常处理,这个主要处理授权异常。
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(AuthorizationException.class)
public ModelAndView error(AuthorizationException e) {
ModelAndView mv = new ModelAndView("unauthorized");
mv.addObject("error", e.getMessage());
return mv;
}
}