SSM
Spring+Spring MVC+MyBatis
搭建项目
1.创建Maven项目,并检查Maven配置
2.创建junit测试框架
<!-- 导入junit测试框架--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <!-- 指定测试类的适用范围在test目录下--> <scope>test</scope> </dependency>
3.在test.java目录下创建测试类
public class SpringTest { //运行测试方法前运行该方法 @Before public void before() { System.out.println("优先执行"); } //表明该方法是测试方法,可以直接运行,不能有返回值和形参 @Test public void test() { System.out.println("hello Spring"); } //运行测试方法后运行该方法 @After public void after() { System.out.println("最后执行"); } }
4.导入Spring
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.5</version> </dependency>
Spring
-
是一个轻量级(搭建容易)的开源框架
-
降低开发应用各层间的耦合度(模块与模块之间的关联强度,只能降低,不能消除)
-
是一个IOC和AOP容器框架
IOC:控制反转和依赖注入
AOP:面向切面编程
容器:包含并管理应用对象的生命周期
IOC
-
IOC:控制反转,将对象创建的权利交给Spring进行创建
-
IOC容器:是Spring的核心实现,用作于对象的存储,创建和管理一系列对象从创建到销毁的生命周期的过程
IOC实现
1.编写Spring配置文件,配置类的信息,并指定bean(对象)的id,在resource目录下,创建Spring配置文件(XML Configuration File中的Spring Config文件)
<!-- 通过bean标签,装配类的信息 class:类的全路径 id:bean的唯一标识,就是对象在IOC容器中的名字 --> <bean class="com.moon.ioc.demo02.User" id="user"></bean> <bean class="com.moon.ioc.demo02.User" id="user1"></bean>
2.根据配置文件,创建IOC容器,在Spring中提供了两种IOC实现方式
-
BeanFactory接口:是Spring中最顶级的接口,是Spring实现的基本方式,拥有对象的创建和提供的功能
-
ApplicationContext接口:是BeanFactory接口的子接口,拥有更多的功能,包括事务,安全等功能
-
实现类
1.FileSystemXmlApplicationContext:通过读取本地磁盘中的配置文件创建IOC容器
2.ClassPathXmlApplicationContext:通过读取类目录下的配置文件创建IOC容器
3.AnnotationConfigApplicationContext:通过读取有@Configuration注解的配置类来创建IOC容器
-
3.IOC容器通过读取配置文件,再通过反射进行创建对象
@Test public void test02() { //1.创建IOC容器,通过ClassPathXmlApplicationContext实例化ApplicationContext接口来创建IOC容器 // 配置文件路径 通过IO读取文件 创建IOC容器 通过容器创建对象 ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");// 读取配置文件,创建了IOC,创建了对象 //2.获取对象 // 通过bean的id获取对象,第一个参数为id,第二个参数为对象的类型 User user = ioc.getBean("user", User.class); System.out.println(user); User user1 = ioc.getBean("user1", User.class); System.out.println(user1); // 注意:xml中一个id表示一个对象,在测试类中,调用的是哪个id的对象,就将该对象赋给测试类中的对象 }
IOCbean管理
由IOC创建的对象称为bean
IOC的bean管理主要包含一下条件
-
创建bean:反射,加载类对象,默认调用无参构造,创建对象
-
依赖注入(DI):给对象的属性赋值和建立bean与bean之间的关系
-
通过构造方法
-
通过setter方法
-
-
bean管理的实现方式
-
通过xml配置文件
-
基于注解(常用)
-
基于java配置类实现(常用于配置拦截器,过滤器,Mybatis分页配置等)
-
基于xml配置文件管理
-
通过构造方法初始化bean的属性值
<!-- 使用有参构造初始化属性值,使用constructor-arg设置有参构造属性值--> <bean class="com.moon.ioc.demo01.User" id="user"> <!-- name表示属性,value表示值--> <constructor-arg name="name" value="zhangsan"></constructor-arg> <constructor-arg name="age" value="20"></constructor-arg> </bean> <bean class="com.moon.ioc.demo01.User" id="user2"> <!-- index是指将有参构造的参数放在一个数组中,index就是参数的下标索引--> <constructor-arg index="0" value="lisi"></constructor-arg> <constructor-arg index="1" value="18"></constructor-arg> </bean> <bean class="com.moon.ioc.demo01.User" id="user3"> <!-- type是通过参数类型进行匹配--> <constructor-arg type="java.lang.String" value="wangwu"></constructor-arg> <constructor-arg type="java.lang.Integer" value="14"></constructor-arg> </bean>
-
通过setter方法设置bean的属性值
<!-- 通过setter方法设置属性值,使用property调用setter方法设置属性值--> <!-- 首先调用无参构造创建对象,再设置属性值--> <bean class="com.moon.ioc.demo01.User" id="user4"> <property name="name" value="zhaoliu"></property> </bean>
-
注入外部bean(当类之间存在调用时)
<bean class="com.moon.ioc.demo02.Student" id="student"> <property name="name" value="moon"></property> <property name="sex" value="男"></property> <!--通过注入外部bean来设置引用数据类型的属性值,需要先装配引用数据类型的属性值,然后用ref引用其他bean的id--> <property name="course" ref="course"></property> </bean> <bean class="com.moon.ioc.demo02.Course" id="course"> <property name="courseNo" value="501"></property> <property name="courseName" value="java"></property> </bean>
-
注入内部bean(当类之间存在调用时)
<bean class="com.moon.ioc.demo02.Student" id="student2"> <!-- 注入内部bean来设置引用数据类型的属性值--> <property name="course"> <!-- 内部注入不需要id值--> <bean class="com.moon.ioc.demo02.Course"> <property name="courseNo" value="401"></property> <property name="courseName" value="html"></property> </bean> </property> </bean> <!--通过级联关系,给内部bean设置属性值--> <bean class="com.moon.ioc.demo02.Student" id="student2"> <property name="course"> <bean class="com.moon.ioc.demo02.Course"></bean> </property> <property name="course.courseName" value="html"></property> <property name="course.courseNo" value="401"></property> </bean>
-
自动装配
<!-- 自动装配IOC中的其他bean autowire: byType:在此IOC中寻找同类型的bean进行装配,只有当一个类型唯一时,才能自动注入,当出现多个同类型的bean时,可根据name注入 byName:根据name注入,是根据类中set方法的方法名与bean的id进行匹配 setCourse就注入id为course的bean setCourse2就注入id为course2的bean --> <bean class="com.moon.ioc.demo02.Student" id="student" autowire="byName"></bean> <bean class="com.moon.ioc.demo02.Course" id="course"> <constructor-arg name="courseNo" value="111"></constructor-arg> <constructor-arg name="courseName" value="java"></constructor-arg> </bean> <bean class="com.moon.ioc.demo02.Course" id="course2"> <constructor-arg name="courseNo" value="222"></constructor-arg> <constructor-arg name="courseName" value="H5"></constructor-arg> </bean>
-
装配第三方bean
1.导入依赖
2.数据库连接池
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/hqdata?useSSL=false"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
@Test public void test04() throws SQLException { //通过xml获取对象 ApplicationContext ioc = new ClassPathXmlApplicationContext("beanSql.xml"); DruidDataSource dataSource = ioc.getBean("dataSource", DruidDataSource.class); //获取连接 DruidPooledConnection connection = dataSource.getConnection(); String sql = "select * from item_info"; PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery(); while (rs.next()) { System.out.println(rs.getString("name")); } }
-
为数组,集合类赋值
<bean class="com.moon.ioc.demo03.People" id="people"> <property name="girlFriendName" value="moon"></property> <!-- 给数组赋值--> <property name="hobbies"> <array> <value>唱</value> <value>跳</value> <value>rap</value> <value>篮球</value> </array> </property> <!-- 给list赋值--> <property name="skills"> <list> <value>唱</value> <value>跳</value> <value>rap</value> <value>篮球</value> </list> </property> <!-- 给set赋值--> <property name="names"> <set> <value>jisoo</value> <value>jennie</value> <value>rose</value> <value>lisa</value> </set> </property> <!-- 给map赋值--> <property name="map"> <map> <entry key="1" value="moon"></entry> </map> </property> </bean>
基于注解管理
常用注解
1.交给IOC管理
-
Component:将类交给IOC管理
-
Repository:功能和Component一样,在Dao层类的上使用,表明该类是Dao层
-
Service:功能和Component一样,在Service层的类上使用,表明该类是Service层
-
Controller:功能和Component一样,在Controller层的类上使用,表明该类是Controller层
2.设置属性值
-
Value:用在属性上,为属性赋初值
3.依赖注入
-
Autowired:当存在引用数据类型时使用,在IOC中根据类型寻找相应的实现类,并进行注入
-
Qualifier("userDaoImpl"):根据名字寻找,一般和@Autowired连用
-
Resource:设置属性值type或name,指定匹配方式
4.设置bean的作用域
-
Scope:singleton表示单例,prototype表示多例
5.配置类
-
Configuration:表明该类是一个配置类
-
ComponentScan:用于扫描包下注解,将某些类放入IOC容器
-
Bean:在配置类中,将对象交个IOC
注解管理
// pojo包中定义的实体类 @Data @Component("U")//将User交给IOC管理,id默认是首字母小写的驼峰类名,可在括号中自定义id public class User { @Value("zhangsan")//给属性赋初值 private String name; @Value("18") private Integer age; }
// 在service层中调用Dao层的方法 @Service public class UserServiceImpl implements UserService { @Autowired//在IOC中根据类型寻找UserDao的实现类,并进行注入 @Qualifier("userDaoImpl")//根据名字寻找,一般和@Autowired连用 private UserDao userDao; public void test() { userDao.queryUser(); } }
// 在controller层调用service层的方法 @Controller public class UserController { // type属性:根据类型匹配 // name属性:根据id匹配 @Resource(type = UserServiceImpl.class) private UserService userService; public void test() { userService.test(); } }
<!-- 扫描包,检查类是否添加了@Component注解,若有,则将该类交给IOC管理 base-package:指定要扫描的包 --> <context:component-scan base-package="com.moon.ioc"/>
基于java配置类管理
// 定义了一个java配置类 // 相当于一个xml文件 @Configuration// 表明该类为一个配置类 @ComponentScan(basePackages = "com.moon.ioc")// 扫描指定包下的注解,相当于xml中<context:component-scan base-package="com.moon.ioc"/> public class IOCConfig { // 将User对象交给IOC,相当于<bean class="....User" id="user"> // 方法名就是id,也可在Bean注解的括号内自定义 @Bean public User user(@Autowired Course course) {// (@Autowired Course course)表示注入Course属性 User user = new User();// 创建User对象并返回 // 赋值 user.setName("lisi"); user.setAge(20); // bean注入 user.setCourse(course); return user; } @Bean public Course course() { Course course = new Course(); course.setName("java"); return course; } }
@Test public void test01() { // 使用AnnotationConfigApplicationContext类获取IOC容器 ApplicationContext ioc = new AnnotationConfigApplicationContext(IOCConfig.class); User user = ioc.getBean("user", User.class); System.out.println(user); }
IOC生命周期
bean的历程:bean创建初始化---->使用bean---->bean的销毁
bean的作用域:在类上使用Scope注解,当它的value值为singleton时(默认),表示单例;当值为prototype时,表示多例(原型),每次从IOC中获取对象都会创建一个对象
AOP
概念
1.AOP术语
-
切面
-
通知
-
连接点
-
切入点
2.通知类型
-
before:前置通知,方法执行前增强
-
after-returning:返回通知,方法执行完后或执行return后进行增强
-
after-throwing:异常通知,方法抛异常时进行增强(当使用了try...catch后,该通知不触发)
-
after:最终通知,方法执行完或者发生异常时进行增强(相当于after-returning和after-throwing的结合,当after和after-throwing同时存在时,after-throwing先执行,执行完后仍会执行after)
-
around:环绕通知
无参的around
@Around("pointcut()") public void around(ProceedingJoinPoint joinPoint) {// 该参数封装了当前被增强的方法 try { System.out.println("前置通知"); joinPoint.proceed();// 调用被增强的方法,拥有调用被代理类中方法的实际权力 System.out.println("后置通知"); } catch (Throwable throwable) { System.out.println("异常通知"); } finally { System.out.println("最终通知"); } }
有参的around
@Aspect @Component public class RegisterAspect { @Around("execution(* com..*.RegisterImpl.register(String,String))") public void judgeName(ProceedingJoinPoint joinPoint) { try { // getArgs()获取参数值 Object[] args = joinPoint.getArgs(); if (args != null) { String name = (String) args[0]; String pass = (String) args[1]; if (name.length() > 1 && name.length() < 12) { if (pass.length() > 1 && pass.length() < 12) { joinPoint.proceed(); } else { System.out.println("注册失败"); } } else { System.out.println("注册失败"); } } } catch (Throwable throwable) { throwable.printStackTrace(); } } }
有返回值的方法
@Around("execution(* com..*.AccountService.getPassword())") public Object getPass(ProceedingJoinPoint joinPoint) { try { String string = joinPoint.proceed().toString();// 获取方法原本该返回的值 System.out.println("方法应该的返回值为"+string); } catch (Throwable throwable) { throwable.printStackTrace(); } return "@@@";// 修改返回值 }
3.切入点表达式 execution
<!-- 格式:execution(权限修饰符 返回值 包名.类名.方法名(参数)) 1.权限修饰符:可不写,表示匹配所有权限修饰符 2.返回值:*是返回值,表示匹配所有返回值类型 3.包名:com.*表示匹配的是com的下一级包;com..*表示匹配的是com下所有层级的包 4.类名:*表示匹配所有类;*Impl可用于匹配所有的实现类,即以Impl结尾的类 5.方法名:*(..)表示匹配所有的方法;*(String)表示匹配所有参数类型为String的方法;*add(..)表示匹配所有方法名以add结尾的方法 --> execution(* com.moon.demo02.CalculateImpl.add())
基本使用
1.导入Aop相关依赖
<!-- 同包下的需要统一版本--> <properties> <spring-version>5.3.5</spring-version> </properties> <dependencies> <!-- spring--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <!-- Aop解析切入点表达式--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> <!-- Aop切面--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring-version}</version> </dependency> </dependencies>
2.Aop实现的两种方式
-
基于xml
定义一个接口,并创建它的实现类,即被代理的类,当我们需要增强类的某个方法时,就需要定义一个切面类,即代理类,在切面类中实现方法增强的内容,最后通过xml配置文件,将切面类和被代理的类联系起来
被代理的类
@Component public class CalculateImpl implements Calculate{ @Override public void add() { System.out.println("执行加法"); } }
代理类
// 切面 编写方法增强的代码 @Component("aspect") public class AOPAspect { // 方法执行增强 public void before() { System.out.println("时间执行:"+ LocalDateTime.now()); } }
配置文件
<!-- 扫描,将带有Component注解的类放到IOC容器中--> <context:component-scan base-package="com.moon"/> <!-- 编写Aop配置--> <aop:config> <!-- 配置切面--> <aop:aspect ref="aspect"> <!-- aop:xxx表示在被代理类的方法执行前或执行后增强 method:指定使用切面的哪个方法进行增强 pointcut:指定切入点表达式,指定要增强的方法 即使用“aspect”切面的“before”方法对切入点表达式所匹配到的“add”方法之前进行增强 --> <aop:before method="before" pointcut="execution(* com.moon.demo02.CalculateImpl.add())"></aop:before> </aop:aspect> </aop:config>
测试类
@Test public void test03() { ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml"); Calculate calculate = ioc.getBean("calculateImpl", Calculate.class);// 只能是接口的类型 //Calculate calculate = ioc.getBean("calculateImpl", CalculateImpl.class); // 该方式会报错,原因:使用Aop后,在IOC中获取被代理类的bean,实际上获取的是动态代理创建的代理类的bean,生成的代理类实现的是和被代理类相同的接口,因此,获取bean时指定其接口类型,表示执行的是代理类 calculate.add(); }
-
基于注解
被代理的类
@Repository // Dao层用于交给IOC管理的注解 public class UserDaoImpl implements UserDao{ @Override public void queryUser() { System.out.println("查询"); } }
代理类
@Aspect// 表明是一个切面 @Component// 将切面交给IOC管理 public class UserDaoAspect { // 可将切点表达式单独提出来,达到复用的效果 @Pointcut("execution(* com.moon.demo03.UserDaoImpl.*(..))") public void pointcut() { } @AfterThrowing(value = "pointcut()",throwing = "e")// throwing = "e"表示用e来接收异常信息 public void throwing(Exception e) { System.out.println("出现异常"+e.getMessage()); } @Before("execution(* com.moon.demo03.UserDaoImpl.*(..))") public void before() { System.out.println("日志记录"); } @After("execution(* com..*.*DaoImpl.*(..))") public void after() { System.out.println("记录结束"); } }
配置文件
<context:component-scan base-package="com.moon"/> <!-- 在切面上加了注解@Aspect后,需要在IOC中扫描--> <aop:aspectj-autoproxy/>
AOP事务
将操作设置为一个整体,要么全部执行完成,要么一个都不执行
<!-- 扫描注解--> <context:component-scan base-package="com.moon"/> <!-- 配置DruidDataSource,用于连接数据库--> <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <!-- 设置数据库信息--> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/hqdata?useSSL=false"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!-- 装配jdbcTemplates,可直接执行sql语句--> <bean class="org.springframework.jdbc.core.JdbcTemplate" name="jdbcTemplate"> <!-- 设置DruidDataSource--> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事务管理器,发生异常时进行回滚--> <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="manager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 开启注解驱动,并配置事务管理器--> <!-- 注意:annotation-driven选择以tx结尾--> <tx:annotation-driven transaction-manager="manager"/>
@Repository("jdbcDaoImpl")// 将类交给IOC管理 public class JDBCDaoImpl implements JDBCDao { @Autowired// 根据类型注入,IOC中存在JdbcTemplate的实现类 JdbcTemplate jdbcTemplate; @Override @Transactional(rollbackFor = Exception.class)// 开启事务管理,并在括号中指定发生异常时进行回滚 public void deleteUser() { // 不能使用try...catch处理异常,若使用事务管理器不会检测到异常,因此不会执行回滚 // 执行sql操作 int i = jdbcTemplate.update("delete from user1 where id=13"); // System.out.println(1/0);// 发生异常,回滚 System.out.println(i>0?"删除成功":"删除失败"); } @Override @Transactional(rollbackFor = Exception.class,readOnly = true)// readOnly=true时只能执行DQL操作,不能执行DML操作 public void updateUser() { int i = jdbcTemplate.update("update user1 set name='李四' where id=6"); System.out.println(i>0?"更新成功":"更新失败"); } }
MyBatis
对JDBC进行了封装,是一个持久层框架,支持自定义SQL,存储过程以及高级映射
映射:将数据库查询出来的字段名和java实体类的属性进行一一对应,使数据库表和实体类进行映射
搭建
1.导入依赖
<!-- Mybatis依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <!-- mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <!-- lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> </dependency>
2.配置数据库连接信息
创建jdbc.properties,在该文件中写相关配置信息
#驱动 driver=com.mysql.jdbc.Driver #连接地址 url=jdbc:mysql://localhost:3306/hqdata?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=Asia/shanghai #用户名 username=root #密码 password=root
3.创建核心配置文件
在resource目录下创建mybatis.xml核心配置文件,配置数据库连接环境,配置mapper映射文件
其中,mybatis.xml的格式如下
properties
读取jdbc.properties,在configuration标签内添加properties标签
<!-- 读取jdbc.properties配置文件,获取数据库连接信息--> <properties resource="jdbc.properties"/>
settings
settings标签内进行配置
<settings> <!-- 配置sql日志打印--> <setting name="logImpl" value="STDOUT_LOGGING"/> <!-- 下划线驼峰映射,数据库中stu_name映射到stuName--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
typeAliases
<!-- 设置映射类型别名--> <typeAliases> <!-- 设置pojo包下的类型别名,别名默认为类名和首字母小写的类名,因此在映射文件的resultType中可以直接写别名--> <package name="com.moon.pojo"/> </typeAliases>
mappers
在mapper标签的resource属性中填写mapper.xml映射文件的地址
<mappers> <mapper resource="com/moon/mapper/UserMapper.xml"/> </mappers>
4.创建实体类
创建pojo实体类包,并在pojo包下创建表对应的实体类,其中实体类的属性名和类型都要和数据库表中字段相同
5.创建Mapper接口和相应的映射文件
Mapper接口
创建mapper包,并在mapper包下创建接口,在Mapper接口中声明SQL方法。
不需要手动创建接口的实现类,因为会通过代理自动创建该接口的代理实现类,但是需要创建mapper映射文件关联该接口,并编写接口方法对应执行的sql语句
Mapper映射文件
在resource目录下创建mapper接口的映射文件,映射文件名必须与mapper接口名相同,且映射文件所在的目录结构必须和接口目录接口一样(在resource下新建映射文件的目录结构时用/)。
其中mapper标签的namespace属性为Mapper接口的全路径
实例
(1)根据id查询
mapper接口
User queryUserById();// 根据数据库的字段自动映射到pojo类中
mapper映射文件
<!--编写sql语句:使用select标签, 其中id:要和接口中方法名一致,表明方法对应的sql操作; resultType:指定sql操作返回的映射类型,需要些实体类的全路径--> <select id="queryUserById" resultType="com.moon.pojo.User"> select * from user where id=1 </select>
(2)查询多条数据
mapper接口
List<User> queryAllUser();// 需要查询多条数据时,返回值为List
mapper映射文件
<!--当返回值为list时,resultType为List<User>中的User类型--> <select id="queryAllUser" resultType="com.moon.pojo.User"> select * from user </select>
(3)返回值为Map
mapper接口
Map<String,String> queryUserMap();// 返回值为Map类型,键为字段名,值为数据
mapper映射文件
<!--当返回值为map时,resultType类型为map--> <select id="queryUserMap" resultType="java.util.Map"> select * from user where id=1 </select>
(4)带参查询
mapper接口
User queryUserById(Integer id);
mapper映射文件
<!--带参数的sql语句,其中#内为接口方法中形参的名称--> <select id="querUserById" resultType="com.moon.pojo.User"> select * from student where id=#{id} </select>
(5)带多参数
mapper接口
Student queryStudentByNameAndAge(String name,Integer age);// 多参数时,会将参数放到hashmap中,即arg0=name,arg1=age //可通过@Param注解来修改键 //Student queryStudentByNameAndAge(@Param("name") String name,@Param("age") Integer age);
mapper映射文件
<!--带多个参数时,是在hashmap中通过key值获取,所以#内为arg0,arg1等--> <select id="queryStudentByNameAndAge" resultType="Student"> select * from student where stu_name=#{arg0} and stu_age=#{arg1} </select>
(6)更新
mapper接口
Integer updateUserById();
mapper映射文件
<!--当执行DML操作时,返回值都是影响行数,所以不需要指定返回值类型--> <update id="updateUserById"> update user set name='王五' where id=6 </update>
(7)实体参数
mapper接口
Integer insertStu(Student student);
mapper映射文件
<!--当参数是实体类时,#{}中为实体类的属性,测试类中可新建Student,传入后会自动映射--> <!--useGeneratedKeys="true":表示获取数据插入后自增的id;keyProperty="stuId":表示使用stuId属性接收id--> <insert id="insertStu" useGeneratedKeys="true" keyProperty="stuId"> insert into student values(null,#{stuName},#{stuAge},#{stuSalary},#{stuBirth},now()) </insert>
(8)实体参数和其他参数
mapper接口
List<Student> queryStudentLimit(@Param("student") Student student,@Param("limit") Integer limit);
mapper映射文件
<!--如果同时存在实体类和其他类型参数时,使用#{key.属性名}获取参数值--> <select id="queryStudentLimit" resultType="Student"> select * from student where stu_age=#{student.stuAge} limit #{limit} </select>
(9)模糊查询
mapper接口
List<Student> queryStudentByNameLike(String name);
mapper映射文件
<!--使用concat函数来连接字符串--> <select id="queryStudentByNameLike" resultType="Student"> select * from student where stu_name like concat('%',#{name},'%') </select>
(10)自定义结果集
当实体类中存在别的引用数据类型
mapper接口
//查询学生以及课程信息 List<Student> queryStudentAllInfo();
查询语句
<!--当使用自定义结果集时,resultType改为resultMap,其中值就是自定义结果集的id--> <select id="queryStudentAllInfo" resultMap="studentInfo3"> SELECT student.*,stu_course.course_name FROM `student`left join stu_course on student.course_id=stu_course.course_id </select>
方式一:使用级联关系
<resultMap id="studentInfo" type="student"> <!-- column是指数据库中的字段,property是实体类中的属性名,指数据库中的字段映射到实体类中哪个属性--> <!-- 设置主键映射--> <id column="stu_id" property="stuId"/> <!-- 设置其他映射--> <!-- 设置student表中信息属性映射--> <result column="stu_name" property="stuName"/> <result column="stu_age" property="stuAge"/> <result column="stu_salary" property="stuSalary"/> <result column="stu_birth" property="stuBirth"/> <result column="create_time" property="createTime"/> <result column="course_id" property="courseId"/> <!-- 设置course表中信息属性映射--> <!-- course.courseId中course表示Student类中的属性,courseId表示Course类中的属性--> <result column="course_id" property="course.courseId"/> <result column="course_name" property="course.courseName"/> </resultMap>
方式二:association
<resultMap id="studentInfo2" type="student"> <id column="stu_id" property="stuId"/> <result column="stu_age" property="stuAge"/> <result column="stu_name" property="stuName"/> <!-- 使用association自定义引用数据类型的映射关系--> <!-- property指Student类中的course属性,它的类型是实体类stuCourse--> <association property="course" javaType="stuCourse"> <id column="course_id" property="courseId"/> <result column="course_name" property="courseName"/> </association> </resultMap>
方式三:分步查询
java代码思路
@Test public void test09() { StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); CourseMapper courseMapper = sqlSession.getMapper(CourseMapper.class); // 1.查询全部学生(使用StudentMapper中的查询全部学生的方法) List<Student> students = mapper.queryAllStudent(); // 2.遍历查询结果 for (int i = 0; i < students.size(); i++) { // 3.获取课程id Integer courseId = students.get(i).getCourseId(); // 4.使用courseId查询course信息(使用CourseMapper中通过id查询课程的方法) StuCourse stuCourse = courseMapper.queryCourseById(courseId); // 5.将查询到的course设置到每个student对象 students.get(i).setCourse(stuCourse); } for (Student student : students) { System.out.println(student); } }
自定义结果映射
<!-- 过程: 1.先查询student表,获取到结果后遍历结果,获取每条数据的course_id 2.获取到course_id后,执行select * from stu_course where course_id=查询到的course_id 3.查询到课程信息后,设置到course属性中--> <resultMap id="studentInfo3" type="student"> <id column="stu_id" property="stuId"/> <result column="stu_age" property="stuAge"/> <result column="stu_name" property="stuName"/> <!-- select:指定分步查询要执行的sql column:分步查询方法所需要的参数值--> <association property="course" select="com.moon.mapper.CourseMapper.queryCourseById" column="course_id"> <id column="course_id" property="courseId"/> <result column="course_name" property="courseName"/> </association> </resultMap>
(11)动态sql
-
①if标签
mapper接口
List<Student> queryStudentByPrams(Student student);// 根据学生属性查询学生
mapper映射文件
<!-- where标签,将条件写在where标签内部,自动添加where关键字,可解决格式问题--> <!-- if标签: 格式: <if test="判断条件">sql</if> --> <select id="queryStudentByPrams" resultType="student"> select * from student <where> <!--传入student,在测试类中新建student,并为它的属性添加值,当它的属性值不为空时,会进入判断--> <if test="stuId!=null">and stu_id=#{stuId}</if> <if test="stuName!=null">and stu_name=#{stuName}</if> </where> </select>
-
②choose标签
mapper接口
List<Student> queryStudentByPrams2(Student student);
mapper映射文件
<!-- choose标签:相当于switch...case,当前面的满足条件,即是后面的满足,也不会执行 格式: <choose> <when test="条件">sql</when> <when test="条件">sql</when> <otherwise>sql</otherwise> </choose> --> <select id="queryStudentByPrams2" resultType="student"> select * from student <where> <choose> <when test="stuId!=null"> and stu_id=#{stuId} </when> <when test="stuName!=null and stuName!=''"> and stu_name=#{stuName} </when> <otherwise> and stu_age=#{stuAge} </otherwise> </choose> </where> </select>
-
③foreach标签
mapper接口
// 批量插入 Integer insertStudentBatch(@Param("students") List<Student> students);// 当参数为list类型时需要设置别名 // 批量删除 Integer deleteStudentBatch(@Param("stuIds") List<Integer> stuIds);
mapper映射文件
<!-- foreach标签 collection:需要遍历的集合 item:当前遍历到的值 separator:以什么进行分割 open:表示从哪里开始 close:表示从哪里借结束 --> <!-- 实现批量插入--> <insert id="insertStudentBatch"> insert into student(stu_id,stu_name,stu_age) values <foreach collection="students" item="one" separator=","> (null,#{one.stuName},#{one.stuAge}) </foreach> </insert> <!-- 实现批量删除--> <!-- delete form student where stu_id in(6,7,8)--> <delete id="deleteStudentBatch"> delete from student where stu_id in <foreach collection="stuIds" item="id" open="(" close=")" separator=","> #{id} </foreach> </delete>
6.测试类
//Mybatis提供数据库操作的核心接口:SqlSession,通过工厂模式创建 @Test public void test01() throws IOException { // 一系列初始化操作,可将该一系列操作提到一个方法中,便于复用 // 1.读取mybatis核心配置文件转化为流 InputStream in = Resources.getResourceAsStream("mybatis.xml"); // 2.实例化SqlSessionFactoryBuilder(新建工人,用于造工厂) SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); // 3.加载核心配置文件,实例化SqlSessionFactory(用工人建造工厂,用于造SqlSession) SqlSessionFactory factory = builder.build(in); // 4.获取SqlSession(工厂建造完成,造SqlSession) SqlSession sqlSession = factory.openSession(); // 5.给接口创建代理实现类对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 6.调用接口方法,执行对应sql操作 User user = mapper.queryUserById(); System.out.println(user); // 更新操作 // 当执行DML操作时,需要提交事务,自动提交是默认关闭的,需要手动提交事务 Integer i = mapper.updateUserById(); System.out.println(i>0?"更新成功":"更新失败"); sqlSession.commit();// 手动提交 // 也可在SqlSession sqlSession = factory.openSession(true);括号内参数设置为true,也可自动提交 }
分页功能
1.导入分页插件的依赖
<!-- 分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.1.4</version> </dependency>
2.在mybatis.xml文件的响应位置,配置分页插件
<!-- 配置分页插件--> <plugins> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> </plugin> </plugins>
3.测试类
public void test14() { StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); //1.开启pageHelper分页功能,在查询数据前开启(会拦截sql语句) 设置当前页 每页条数 PageHelper.startPage(2,3); //2.获取到全部数据 List<Student> students = mapper.queryAllStudent(); //3.创建PageInfo对象,将数据进行分页处理,传入查询到的学生信息 PageInfo<Student> studentPageInfo = new PageInfo<>(students); //4.获取当前页码的数据 List<Student> list = studentPageInfo.getList(); for (Student student : list) { System.out.println(student); } }
分页功能的常用API
public void test06(){ PageHelper.startPage(1,2);//表示第一页 每页显示两条数据 List<User> users = mapper.queryUserByPage();// 查询 PageInfo<User> pageInfo = new PageInfo<>(users);// 将查询数据交给分页管理 System.out.println("当前页的数据:"+pageInfo.getList()); System.out.println("当前页码:"+pageInfo.getPageNum()); System.out.println("每页显示条数:"+pageInfo.getPageSize()); System.out.println("当前页实际显示条数:"+pageInfo.getSize()); System.out.println("总记录数:"+pageInfo.getTotal()); System.out.println("总页数:"+pageInfo.getPages()); System.out.println("上一页的页码:"+pageInfo.getPrePage()); System.out.println("下一页的页码:"+pageInfo.getNextPage()); System.out.println("是否为第一页:"+pageInfo.isIsFirstPage()); System.out.println("是否为最后一页:"+pageInfo.isIsLastPage()); System.out.println("是否存在上一页:"+pageInfo.isHasPreviousPage()); System.out.println("是否存在下一页:"+pageInfo.isHasNextPage()); }
缓存
存在内存中的临时数据,缓存能够减少与数据库的交互次数,提高效率。将相同查询条件的sql语句执行一遍后得到的结果存在内存或者某种缓存介质中,当下次遇到一模一样的查询sql时候不在执行sql与数据库交互,而是直接从缓存中获取结果,减少服务器的压力
一级缓存
默认开启,作用范围为同一个sqlSession中执行相同的sql查询,第一次时查询数据库,并存到缓存中,第二次从缓存中提取
@Test public void test15() throws IOException { InputStream in = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); SqlSession sqlSession = factory.openSession(true); SqlSession sqlSession1 = factory.openSession(true); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class); // 只需要执行一次数据库查询操作 Student student = mapper.queryStudentById(1); Student student1 = mapper.queryStudentById(1); // 需要执行两次查询操作 Student student = mapper.queryStudentById(1); Student student1 = mapper1.queryStudentById(1); }
失效条件
-
执行相同的SQL,但是传递的参数值不同
-
相同的SQL,但是位于不同的Mapper.xml中
-
执行了insert、update、delete操作后,会将缓存刷新
-
手动清理缓存。sqlsession.clearCache()
二级缓存
默认关闭,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,二级缓存是跨 SqlSession 的
SpringMVC
M:model,数据模型层。分为两类:1.一般实体类Bean,即Student类,User类 2.业务处理类Bean,即Service,Dao
V:view,视图层。指.html,.jsp等页面,和用户交互,数据渲染展示
C:Controller,控制层。指工程中的Servlet,接收处理请求控制页面的跳转或响应数据给浏览器
Spring工作流程
SpringMVC项目搭建
1.导入依赖
<dependencies> <!-- springmvc依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.5</version> </dependency> <!-- servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> </dependency> </dependencies>
2.转换成Web项目
项目根目录 --> 右键选择Add Framework Support... --> 勾选Web Application --> 转换成web项目后,需要手动导入依赖 --> Project Structure --> Artifacts --> 在WEB-INF目录下新建lib目录 --> 选中依赖,右键点击put into
3.在resource目录下新建spring.xml文件(IOC容器)
视图解析器的作用是,当controller层返回视图时,就不需要写视图的全路径名,只需写视图文件名,会自动拼接
<!-- 扫描类上注解,将有相应注解的类交给IOC管理--> <context:component-scan base-package="com.moon"/> <!-- 配置视图解析器:在返回视图时,只需返回视图文件名,自动拼接成/templates/视图文件名.jsp--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="resolver"> <!-- 设置前缀--> <property name="prefix" value="/templates/"/> <!-- 设置后缀--> <property name="suffix" value=".jsp"/> </bean>
4.在web.xml注册DispatcherServlet
<!-- 注册DispatcherServlet--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 初始化DispatcherServlet时,加载spring_mvc.xml配置文件--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring_mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <!-- 指定servlet处理的请求有哪些,/ 表示处理所有请求--> <url-pattern>/</url-pattern> </servlet-mapping>
配置编码过滤器
<!-- 配置编码过滤器,解决请求和响应的中文乱码问题--> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!-- 设置编码为utf-8--> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> <!-- 设置请求和响应都生效--> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <!-- 设置过滤器的请求映射(什么样的请求才会执行该过滤)--> <filter-mapping> <filter-name>encodingFilter</filter-name> <!-- 表示处理任何请求--> <!-- <url-pattern>/</url-pattern>--> <!-- 表示过滤dispatcherServlet处理的请求,也相当于所有请求--> <servlet-name>dispatcherServlet</servlet-name> </filter-mapping>
5.添加tomcat(注意在Deployment中添加包,并修改路径)
RequestMapping注解
以前是用servlet来访问页面,现在是通过控制层的方法来访问
属性:
-
value:指定映射路径,是String[]类型,因此可以同时设置多个映射路径
-
method:指定请求方法[POST,GET],是数组类型,可以同时指定多个请求方式。若没有指定请求方式,默认可以接收任何请求
-
params:指定请求必须携带的参数,携带才能访问,是String[]类型
1.用在方法上
当不存在父路径时,为:地址栏localhost:8080/hello --> 自动映射到hello()方法 --> 执行方法,返回jsp页面给前端
2.用在类上,表示父路径;方法上的为子路径
如:地址栏localhost:8080/view/hello --> 映射到hello()方法 --> 执行方法,返回jsp页面给前端
@Controller // 交给IOC管理 @RequestMapping("/view") public class HelloController { @RequestMapping("/hello")// 地址栏输入/hello映射到该方法 public String hello() { return "hello";// 返回jsp文件路径,找到jsp页面,再返回给前端(在配置了视图解析器后,就只需要返回视图文件名) } // 两个路径都能访问该方法,且必须携带此两个参数 @RequestMapping(value = {"/hello","/hello2"},params = {"username","password"}) public String hello() { return "hello"; } // 当请求为POST时,才能访问该方法 @RequestMapping(value = "/index",method = RequestMethod.POST) public String hello2() { return "index"; } }
获取请求参数
1.使用HttpServletRequest对象获取
@RequestMapping("/test01") @ResponseBody// 表明请求该方法时,不是返回页面,而是返回Json格式数据(return不是去寻找页面,而是将return中的数据返回) public String test01(HttpServletRequest request) { // 获取用户名 String username = request.getParameter("username"); // 获取密码 String password = request.getParameter("password"); System.out.println(username); System.out.println(password); return "success"; }
2.使用方法的参数获取
@RequestMapping("/test02") @ResponseBody public String test02(String username,String password) {// 直接在方法中通过参数获取(在未使用@RequestParam注解设置别名时,方法中的参数名必须和请求的参数名一致) System.out.println(username); System.out.println(password); return "success!"; }
设置了别名
//@RequestParam注解 //属性: //value:指定参数映射名,相当于给方法参数设置别名,设置别名后,请求参数名必须和别名一致 //required:表示该参数是否要传,默认是true,若未传,报400;指定为false时可传可不传,不传时,值默认为null @RequestMapping("/test06") @ResponseBody public String test06(@RequestParam(value = "name",required = false) String username,@RequestParam("pass") String password) { System.out.println(username); System.out.println(password); return "success!!!!!"; }
当参数类型为实体类时
//传参时,请求参数名必须和实体类中的属性名一致 @RequestMapping("/test03") @ResponseBody public String test03(User user) { System.out.println(user); return "success!!"; }
当参数类型为数组
//请求参数为多个strings @RequestMapping("/test04") @ResponseBody public String test04(String[] strings) { System.out.println(Arrays.toString(strings)); return "success!!!"; }
3.使用占位符获取参数值
//如:/test05/{参数1}/{参数2} @RequestMapping("/test05/{name}/{age}")// 请求路径:localhost:8080/data/test05/moon/20 @ResponseBody public String test05(@PathVariable("name")String name,@PathVariable("age")Integer age) {//使用@PathVariable来指定获取哪个参数,并提供一个变量来接收 System.out.println(name); System.out.println(age); return "success!!!!"; }
域对象
Request域对象
ModelAndView:渲染页面和数据(不同的请求获取到的页面不同)
当地址栏/mdv/test01到mdv方法时,执行方法,设置了返回的视图hello.jsp(在页面中添加了域对象${time}),并设置了域对象的数据,所以只有通过该地址访问到hello.jsp页面时,${time}才会有数据,而通过其他方法访问到的hello.jsp都无法显示该数据
@Controller @RequestMapping("mdv") public class RequestController { @GetMapping("/test01") public ModelAndView mdv() { // 创建一个ModelAndView域对象 ModelAndView modelAndView = new ModelAndView(); // 设置视图 modelAndView.setViewName("hello");// 找到hello.jsp页面 // 设置数据 modelAndView.addObject("time", LocalDateTime.now());// 可在jsp页面中通过el表达式展示数据,el表达式为该方法的key值,如${time} return modelAndView; } }
登录注册案例
登录页面
<body> <!--点击提交按钮后,跳转到service层进行判断--> <form action="/login/test" method="post"> 账号:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="登录"> </form> <div style="color: red">${msg}</div> </body>
登陆成功页面
<body> <div>${msg},欢迎您,${username}</div> </body>
controller层
@Controller public class LoginController { @GetMapping("/hello") public String success() { return "hello"; } @GetMapping("/login") public String defaults() { return "login"; } }
service层
@Service @RequestMapping("/login") public class LoginService { @PostMapping("/test") public ModelAndView login(String username,String password) { ModelAndView modelAndView = new ModelAndView(); if ("moon".equals(username) && "123456".equals(password)){ // 设置视图,登录成功,跳转到hello.jsp页面 modelAndView.setViewName("hello"); // 设置数据 modelAndView.addObject("msg","登陆成功"); modelAndView.addObject("username",username); return modelAndView; } else { modelAndView.setViewName("login"); modelAndView.addObject("msg","账号或密码错误"); return modelAndView; } } }
Session域对象
@Controller @RequestMapping("/session") public class SessionController { @GetMapping("/testSession") public String testSession(HttpSession session) { // 1.设置session数据 session.setAttribute("value","欢迎访问!");// 设置session域对象,在页面添加该域对象后(${sessionScope.value}),通过各种请求都可以访问到该数据 // 2.渲染视图 return "hello"; } }
Rest风格
设计一个请求风格
1.如何扫描请求方法
2.如何制定请求路径
3.如何传参
//保密性:令路径相同,请求方式不同,进入不同的方法,执行不同的操作 public class RestController { //通过id查询用户 @GetMapping("/user/{id}")//表示是一个get请求 public void queryById(@PathVariable("id")Integer id) { System.out.println("查询"+id+"的用户"); } //查询全部用户 @GetMapping("/users") public void queryAll() { System.out.println("全部用户"); } //新增 @PostMapping("/users")//post请求,避免信息暴露在地址栏 public void insertUser() { System.out.println("添加"); } //删除 @DeleteMapping("/user/{id}") public void deleteUser(@PathVariable("id")Integer id) { System.out.println("删除"+id+"的用户"); } //更新 @DeleteMapping("/user/{id}") public void updateUser(@PathVariable("id")Integer id) { System.out.println("更新"+id+"的用户"); } }
转发和重定向
@Controller public class TestController { @RequestMapping("/test") public String test() { return "test"; } // 重定向,没有共享一个request域,因为是两次请求 @RequestMapping("/test01") public String test01() { return "redirect:/test";// ①访问/test01,执行该方法 ②重定向,返回页面,重新请求/test ③执行顶部方法,返回test页面 } // 请求转发,共享一个request域 @RequestMapping("/test02") public String test02() { return "forward:/test";// ①访问/test02,执行该方法 ②请求转发,在服务器内部转发到/test ③执行顶部方法,返回test页面 } }
JSON
Content-Type
text/html:浏览器解析页面
application/json:解析json数据
multipart/form:解析文件流
响应json数据
@Controller @RequestMapping("/json") public class JSONResponseController { @GetMapping("/test01") @ResponseBody public User jsonTest01() { User user = new User(); user.setUserName("moon"); user.setUserPass("123456"); user.setSex('男'); // 以json字符串格式返回一个User对象 return user; } }
1.添加依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.1</version> </dependency>
2.添加注解@ResponseBody,表示返回的是数据,而不是一个页面
3.在spring.xml中添加注解驱动
<!-- 配置mvc注解驱动--> <!-- 后缀为mvc的annotation-driven--> <mvc:annotation-driven/>
@RestController
在类上添加,相当于@Controller + @ResponseBody,表示该控制层,默认返回的是json数据
获取json数据
1.参数是一个实体类时
@GetMapping("/test01") public HashMap<String,Object> test01(@RequestBody User user) {// 添加了@RequestBody,表示请求必须是一个json格式的User对象 HashMap<String, Object> map = new HashMap<>(); map.put("code","200"); map.put("value",user);// 获取到传入的参数 return map;// 显示在页面上 }
2.参数既有实体类,又有普通参数时
@PostMapping("/test02") public HashMap<String,Object> test02(@RequestBody User user,String param) {// 混合参数,既有实体类,又有普通参数 HashMap<String, Object> map = new HashMap<>(); map.put("code","200"); map.put("value",user); map.put("param",param); return map; }
在Params中单独添加普通参数
3.参数有两个实体类
// @RequestBody在参数列表中只能只用一次 @PostMapping("/test03") public HashMap<String,Object> test03(@RequestBody User user, Student student) {// 参数为两个实体类时 HashMap<String, Object> map = new HashMap<>(); map.put("code","200"); map.put("value",user); map.put("stu",student); return map; }
将另一个实体类的属性值在Params中设置
json数据交互
实例
携带的参数是json格式
1.地址栏输入/register访问到register.jsp页面
@RequestMapping("/register") public String register() { return "register"; }
2.输入账号密码后,点击注册
注意导入vue.js和jquery.js
<body> <div id="register"> 账号:<input type="text" v-model="user.userName"/><br> 密码:<input type="password" v-model="user.userPass"/><br> <button @click="register">注册</button> </div> <script> new Vue({ el:"#register", data:{ user:{ userName:"", userPass:"", } }, methods:{ register() { var _this = this; // 发送ajax,请求注册接口(即controller中的方法,而不是interface) $.ajax({ url:"/user/register",// 请求路径 type:"post",// 请求方式 contentType:"application/json",// json格式 data:JSON.stringify(_this.user),// 请求携带的参数,将json对象转换成json字符串 dataType:"json",// 返回时参数的格式,将后端响应的json格式的字符串转换成json对象 success:function (response){// 获取后端响应值,即返回的map console.log(response) } }) } } }) </script> </body>
3.点击注册后,执行register()方法,根据ajax请求路径执行java方法
@RestController @RequestMapping("/user") public class UserController { /** * 请求路径:/user/register-----POST * @param user json格式的user对象 * @return json格式的map */ @PostMapping("/register") public HashMap<String,Object> register(@RequestBody User user) { System.out.println(user); // 存放响应给前端的信息,告诉前端请求是否成功 HashMap<String, Object> map = new HashMap<>(); map.put("code","200"); map.put("msg","注册成功"); return map; } }
3.json数据交互
前端控制台输出后端的返回的map提示信息,后端获取到前端所输入的账号和密码
当携带的参数不是json格式时
<script> new Vue({ el:"#register", data:{ user:{ userName:"", userPass:"", } }, methods:{ register2() { var _this = this; $.ajax({ url:"/user/register2", type:"post", // 请求携带的参数不是json类型 data:{ "userName":_this.user.userName, "userPass":_this.user.userPass, }, dataType:"json", success:function (response){ console.log(response) } }) } } }) </script>
@PostMapping("/register2") public HashMap<String,Object> register2(User user) {// 不用添加@RequestBody注解 System.out.println(user); HashMap<String, Object> map = new HashMap<>(); map.put("code","200"); map.put("msg","注册成功"); return map; }
静态资源访问
1.spring.xml中配置静态资源访问映射
<!-- mapping:指定映射路径 location:指定映射的静态资源目录--> <!-- 通过res/请求路径,就能映射访问到static目录下的资源--> <mvc:resources mapping="/res/**" location="/static/"/>
2.在web文件夹下创建static目录,static目录中新建css,img,js文件夹
当要访问static目录下的js文件夹下的jquery.js文件,路径为/res/js/jquery.js
拦截器
拦截器和过滤器的区别
拦截器实现
1.新建config文件夹,创建类并实现HandlerInterceptor接口
public class MyInterceptor implements HandlerInterceptor { // 该方法在dispatcherServlet接收请求后,还未到达controller控制层之前拦截 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return false;// false表示不放行 } // 在控制层接收请求后,还未到达浏览器前进行拦截 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } // 在整个请求结束后进行处理,一般做清理工作 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
2.在spring.xml中注册拦截器
x <!-- 注册拦截器--> <mvc:interceptors> <mvc:interceptor><!-- 表示拦截所有请求--> <mvc:mapping path="/**"/><!-- 可在spring.xml中直接配置放行路径,即白名单--> <!-- <mvc:exclude-mapping path="/login"/>--><!-- <mvc:exclude-mapping path="/user/login"/>--> <bean class="com.moon.config.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors>xml
拦截器实例
jsp页面
<html> <head> <title>登录</title> <script src="/res/js/jquery.js" type="text/javascript"></script> <script src="/res/js/vue.js" type="text/javascript"></script> </head> <body> <div id="login"> 账号:<input type="text" v-model="user.userName"/><br> 密码:<input type="password" v-model="user.userPass"/><br> <button @click="login">登录</button> </div> <script> new Vue({ el:"#login", data:{ user:{ userName:"", userPass:"", } }, methods:{ login() { var _this = this; $.ajax({ url:"/user/login", type:"post", contentType:"application/json", data:JSON.stringify(_this.user), success:function (response){ if (response.code === 200) { alert("登录成功"); } else { alert("账号或密码错误") } } }) } } }) </script> </body> </html>
controller层
@Controller @RequestMapping("/user") public class LoginController { @PostMapping("/login") @ResponseBody public HashMap<String,Object> login(@RequestBody User user, HttpSession session) { // map用于存储返回给前端的信息 HashMap<String,Object> map = new HashMap<>(); if ("moon".equals(user.getUserName()) && "123456".equals(user.getUserPass())) { map.put("code",200); // 设置session域对象,以便于在拦截器中获取,并判断是否成功登录 session.setAttribute("user",user); } else { map.put("code",500); } return map; } }
拦截器
@Override // request封装了被拦截的请求,可以获取请求参数,请求头部信息等 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 用于添加不需要拦截的请求路径 List<String> list = new ArrayList<>(); list.add("/login"); list.add("/user/login"); // 静态资源也是通过请求得到的,jsp页面中需要引入静态资源,所以不能拦截 list.add("/res/js/jquery.js"); list.add("/res/js/vue.js"); String requestURI = request.getRequestURI(); if (list.contains(requestURI)) { return true; } // 在controller层添加了session值,在拦截器中获取 HttpSession session = request.getSession(); Object user = session.getAttribute("user"); // 当session值不为空时,表示成功登录 if (user!=null) { return true; } else { // 未登录时,强制重定向到登录页面 request.getRequestDispatcher("/login").forward(request,response); return false; } }
异常管理
自定义404页面
1.在web.xml中配置404页面
<error-page> <!-- 配置错误代码--> <error-code>404</error-code> <!-- 发生错误代码时,返回的页面--> <location>/404</location> </error-page>
2.在controller层定义方法,当访问路径/404时,返回404页面
@RequestMapping("/404") public String page404(){ return "404"; }
3.自定义404.jsp页面
异常处理
浏览器直接发送请求
如果是浏览器直接发送请求,服务器发生异常,可以通过跳转到500页面进行异常显示
(1)定义跳转的方法
@RequestMapping("/error") public String error(){ return "error"; }
(2)异常处理方法
@ControllerAdvice// 表示该控制层用于处理异常 public class ExceptionHandlerController { // 括号中为发生该异常时进行处理,为Exception时,指所有的异常 @ExceptionHandler(Exception.class) public ModelAndView error(Exception e){// 返回一个ModelAndView表示发生异常后,要进行页面渲染和异常信息渲染 ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("error"); modelAndView.addObject("exceptionMsg",e.getMessage()); return modelAndView; } }
(3)自定义error页面,返回前端页面
ajax发送请求
如果请求通过ajax发送,服务器一旦发生异常,一般不用500页面进行处理,而是将异常信息响应到ajax中进行处理
使用ajax发送请求时,一般类上都是使用的是RestController注解,因此可以判断请求是请求页面还是数据
在异常处理方法中判断是否是ajax请求
@ControllerAdvice// 表示该控制层用于处理异常 public class ExceptionHandlerController { /** * * @param e 发生的异常信息 * @param method 封装了处理请求的控制层中的方法,也就是异常发生所在的位置 * @param response 用于给ajax响应异常信息 * @return */ @ExceptionHandler(Exception.class) public ModelAndView error(Exception e, HandlerMethod method, HttpServletResponse response) throws IOException {// 返回一个ModelAndView表示发生异常后,要进行页面渲染和异常信息渲染 // 获取方法所在类上的RestController注解 RestController annotation = method.getBeanType().getAnnotation(RestController.class); // 注解不为空,表示使用ajax请求的 // 如果是ajax请求,则直接响应异常信息 if (annotation != null) { HashMap<String,Object> map = new HashMap<>(); map.put("msg",e.getMessage()); map.put("code",500); // 将map转换成json格式 ObjectMapper mapper = new ObjectMapper(); String result = mapper.writeValueAsString(map); // 响应给ajax response.getWriter().write(result); return null; } // 为空时,表示是服务器直接发送请求 // 如果是页面请求,返回一个500页面 ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("error"); modelAndView.addObject("exceptionMsg",e.getMessage()); return modelAndView; } }
文件上传下载
导入依赖
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
SSM整合
导入依赖
整合SpringMVC
1.web.xml配置
<!-- 注册dispatcherServlet--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring_mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 字符集过滤--> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>characterEncoding</param-name> <param-value>utf-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <servlet-name>dispatcherServlet</servlet-name> </filter-mapping>
2.spring_mvc.xml配置
<!-- mvc只管理controller层,所以只需扫描controller层--> <context:component-scan base-package="com.moon.controller"/> <!-- 配置视图解析器:便于在controller层返回视图时,只需返回jsp文件的文件名--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" name="viewResolver"> <property name="prefix" value="/templates/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 访问静态资源:为静态资源的访问路径起别名--> <mvc:resources mapping="/res/**" location="/static/"/> <!-- 添加注解驱动--> <mvc:annotation-driven/>
整合MyBatis
在resource目录下新建config.properties、mybatis-config.xml、spring.xml文件
1.config.properties配置
数据库连接的相关信息
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=Asia/shanghai #注意:账号和密码不能用username和password,因为系统会自动查找到计算机的username和password name=root pass=root
2.mybatis-config.xml配置
<configuration> <settings> <!-- 驼峰映射:可由数据库表中字段的下划线映射到实体类中属性的驼峰形式--> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 日志打印--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--实体类别名:在mapper映射文件中编写sql语句时,resultType的类型可以为类名首字母的大小写,而不需要全类名--> <typeAliases> <package name="com.moon.pojo"/> </typeAliases> <!-- 分页插件--> <plugins> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> </plugin> </plugins> </configuration>
3.spring.xml配置
<!-- 导入shiro.xml文件--> <import resource="classpath:shiro.xml"/> <!-- 注解扫描,除controller层外的所有--> <context:component-scan base-package="com.moon"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/> </context:component-scan> <!-- 整合Mybatis--> <!-- 获取config.properties--> <context:property-placeholder location="classpath:config.properties"/> <!-- 配置druid--> <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <!-- 配置数据库连接信息--> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean> <!-- 将SqlSessionFactory交给IOC管理--> <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory"> <!-- 配置数据源--> <property name="dataSource" ref="dataSource"/> <!-- 加载Mybatis核心配置文件--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!-- 配置mapper映射文件:映射文件结构无需和Dao层一一对应--> <property name="mapperLocations" value="classpath:mappers/*.xml"/> </bean> <!-- 将创建Dao接口代理实现类对象,交给IOC进行操作--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 设置dao接口所在包位置--> <property name="basePackage" value="com.moon.dao"/> </bean> <!-- 事务管理器--> <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 事务注解驱动--> <tx:annotation-driven transaction-manager="transactionManager"/>
4.web.xml配置
添加监听器和shiro过滤器
<!-- 使用监听器加载spring配置文件--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 指定监听器加载的配置文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring.xml</param-value> </context-param> <!-- 配置shiro过滤器,用于限制未登录授权的用户仅能访问哪些页面--> <filter> <filter-name>filter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <!-- 过滤映射--> <filter-mapping> <filter-name>filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
项目实例
项目结构
1.在web目录下
-
新建static文件夹(用于存放静态资源,其中含有css,js和img文件夹。由于在spring_mvc.xml中配置了静态资源访问,所以访问静态资源的路径就为/res/xxx,如/res/img/bg.png)
-
新建templates文件夹(用于存放页面,由于也在spring_mvc.xml配置了视图解析器,所以在controller接口中返回视图时,返回值只需要写jsp文件的文件名)
在templates目录下新建resource.jsp文件,在其中可以静态资源文件,此后当其他jsp页面也需要引入静态资源文件时,可以在html标签下通过 <%@include file="resource.jsp" %> 直接引入resource.jsp文件
<head> <title>Title</title> <script src="/res/js/jquery.js" type="text/javascript"></script> <script src="/res/js/vue.js" type="text/javascript"></script> <script src="/res/js/index.js" type="text/javascript"></script> <link href="/res/css/index.css" rel="stylesheet"> </head>
2.在resource目录下
-
新建mappers文件夹(用于映射Dao层接口,编写sql语句。由于在spring.xml中将SqlSessionFactory交给IOC管理时,配置了mapper映射文件的路径,所以不需要再严格按照Dao层接口层层对应)
3.在java目录下
-
config层(拦截器,过滤器等)
-
controller层(提供一个“接口”,用于处理浏览器发送的请求。一般分为返回视图的controller和返回数据的controller)
-
dao层(定义sql查询的接口)
-
pojo层(定义实体类,其中实体类的属性与数据库的字段名一一对象,可用驼峰映射)
-
service层(对数据库查询到的数据进行逻辑处理)
-
utils层(定义一些工具类)
注册功能
register.jsp页面
<style> .el-card{ width: 400px; height: 480px; margin: auto; background-color: rgba(255,255,255,0.5); border: 0px; border-radius: 10px; margin-top: 150px; } body{ background: url("/res/img/bg.jpg") no-repeat center center fixed; -webkit-background-size: cover; -moz-background-size: cover; -o-background-size: cover; background-size: cover; } .el-button{ margin-left: 143px; background-color: deepskyblue; border: 0px; border-radius: 10px; } #prompt{ font-size: 13px; text-decoration:none; } </style> <body> <div id="register"> <el-card class="box-card"> <div slot="header" class="clearfix"> <div style="text-align: center;font-weight: bold;font-size: 20px">注 册</div> </div> <el-form> <el-form-item> <!--v-model表示输入的值保存到data中相应的位置--> <el-input v-model="user.userEmail" placeholder="请输入邮箱"></el-input> </el-form-item> <el-form-item> <el-input v-model="user.userName" placeholder="请输入用户名"></el-input> </el-form-item> <el-form-item> <el-input v-model="user.userPassword" type="password" placeholder="请输入密码" show-password></el-input> </el-form-item> <el-form-item> <el-input v-model="user.userBirth" type="date"></el-input> </el-form-item> <el-form-item> <el-radio v-model="user.userGender" label="男">男</el-radio> <el-radio v-model="user.userGender" label="女">女</el-radio> </el-form-item> <!--@click表示当点击时触发方法--> <el-button type="primary" @click="register">注册</el-button> </el-form> <a id="prompt" href="/login"></a> </el-card> </div> <script> new Vue({ // 作用域 el: "#register", data() { return { //和实体类中的属性名称一致,随ajax发送 user:{ userName:"", userPassword:"", userEmail:"", userGender:"", userBirth:"", } } }, methods: { register() { var _this=this; var u=_this.user; // 注册时,当有一项信息未填写时,进行提示 if(u.userName === ""||u.userEmail === ""||u.userPassword === ""||u.userBirth === ""||u.userGender === "") { // 信息弹框 _this.$message({ type:"warning", message:"请完善信息!!!" }) return } // ajax请求发送到后端 $.ajax({ url:"/user/register",// 请求位置,经过controller层 type: "post",// 请求类型 contentType:"application/json", dataType:"json",// 返回时参数的格式 data:JSON.stringify(_this.user),// 请求携带的参数(上述data中的user) // 成功回调后执行的函数 success:function(result){//result接收后端传回数据 _this.$message({ type:result.code===200?"success":"error", message:result.msg }) if (result.code===200){ setTimeout(function () { // 跳转到登录页面 location.href="/login" },2000)// 延迟2秒 } else { // 找到id为#prompt的标签,并设置样式和内容 $("#prompt").css({'color':'red'}); $("#prompt").html("该邮箱已存在,点击登录"); } } }) } }, mounted() { } }) </script> </body>
Dao层
@Repository// Dao层交给IOC public interface UserDao { // 注册功能:需要先查询数据库中是否存在邮箱,不存在则往数据库中插入新的用户数据 /** * 通过邮箱查询用户及角色信息 * @param userEmail * @return */ User queryUserByEmail(String userEmail); /** * 插入用户数据 * @param user * @return */ Integer insertAnUser(User user); /** * 往中间表插入数据,表示用户的权限 * @param userId * @param roleId * @return */ Integer insertUserRole(@Param("userId") Integer userId,@Param("roleId") Integer roleId); }
mapper映射文件
<mapper namespace="com.moon.dao.UserDao"> <!--useGeneratedKeys为true表示获取自增的属性,keyProperty表示用一个变量来接收--> <insert id="insertAnUser" useGeneratedKeys="true" keyProperty="id"> insert into user values(null,#{userName},#{userPassword},#{userEmail},#{userGender},#{userBirth},now()) </insert> <insert id="insertUserRole"> insert into user_role_rel values(null,#{userId},#{roleId}) </insert> <!-- 自定义映射结果集--> <resultMap id="userInfo" type="user"> <id column="id" property="id"></id> <result column="user_name" property="userName"></result> <result column="user_email" property="userEmail"></result> <result column="user_password" property="userPassword"></result> <result column="user_gender" property="userGender"></result> <result column="user_birth" property="userBirth"></result> <result column="create_time" property="createTime"></result> <collection property="roleList" ofType="role"> <id column="role_id" property="roleId"></id> <result column="role_name" property="roleName"></result> </collection> </resultMap> <select id="queryUserByEmail" resultMap="userInfo"> SELECT u.*,r.role_id,r.role_name FROM user u left join user_role_rel urr on u.id = urr.user_id left join role r on urr.role_id = r.role_id where u.user_email=#{userEmail} </select> </mapper>
service层接口和实现类
@Autowired UserDao userDao; @Override @Transactional(rollbackFor = Exception.class)//发生异常,回滚 public Result register(User user) { // 判断邮箱是否注册 User u = userDao.queryUserByEmail(user.getUserEmail()); if (u!=null) { //邮箱已经注册过 return Result.error("该邮箱已经注册过"); } Integer i = userDao.insertAnUser(user); Integer iRole = userDao.insertUserRole(user.getId(), 3);// 注册默认为用户 return (i>0 && iRole>0)?Result.ok("注册成功"):Result.error("注册失败"); }
controller层
@Autowired UserService userService; @PostMapping("/register") public Result register(@RequestBody User user) { try { return userService.register(user); } catch (Exception e) { e.printStackTrace(); return Result.error(e.getMessage()); } }
登录功能
jsp页面
<style> .el-card{ width: 400px; height: 320px; margin: auto; background-color: rgba(255,255,255,0.5); border: 0px; border-radius: 10px; margin-top: 185px; } body{ background: url("/res/img/bg.jpg") no-repeat center center fixed; -webkit-background-size: cover; -moz-background-size: cover; -o-background-size: cover; background-size: cover; } .el-button{ margin-left: 143px; background-color: deepskyblue; border: 0px; border-radius: 10px; margin-top: 20px; } #prompt{ font-size: 13px; text-decoration:none; } </style> <body> <div id="login"> <el-card class="box-card"> <div slot="header" class="clearfix"> <div style="text-align: center;font-weight: bold;font-size: 20px">登 录</div> </div> <el-form> <el-form-item> <el-input v-model="user.userEmail" placeholder="请输入邮箱"></el-input> </el-form-item> <el-form-item> <el-input v-model="user.userPassword" type="password" placeholder="请输入密码" show-password></el-input> </el-form-item> <el-button type="primary" @click="login">登录</el-button> </el-form> <a id="prompt" href="/register"></a> </el-card> </div> <script> new Vue({ el: "#login", data() { return { user:{ userPassword:"", userEmail:"", } } }, methods: { login() { var _this=this; var u=_this.user; if(u.userEmail === ""||u.userPassword === "") { _this.$message({ type:"warning", message:"请填写有效的邮箱或密码!!!" }) return } $.ajax({ url:"/user/shiroLogin", type: "post", contentType:"application/json", dataType:"json", data:JSON.stringify(_this.user), success:function(result){ // 信息弹框 _this.$message({ type:result.code===200?"success":"error", message:result.msg }) if (result.code===200){ setTimeout(function () { // 登陆成功,跳转到首页 location.href="/index" },2000) } else { // 当邮箱不存在时,在id为prompt的标签处添加提示信息 if (result.msg==="邮箱不存在") { $("#prompt").css({'color':'red'}); $("#prompt").html("该邮箱不存在,点击注册"); } } } }) } }, mounted() { } }) </script> </body>
shiro过滤器类
public class MyShiroRealm extends AuthorizingRealm { @Autowired UserDao userDao; /** * 认证 * @param authenticationToken 是UsernamePasswordToken的接口,包含了邮箱和密码 * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String userEmail = token.getUsername();// 获取要执行登录的邮箱,相当于账号 User user = userDao.queryUserByEmail(userEmail); if (user==null){ // 抛出用户不存在异常,在service中可以捕获该异常,返回用户不存在的信息 throw new UnknownAccountException(); } /* 执行密码判断 user:当前要执行认证的用户对象 user.getUserPassword():当前对象的密码,用于密码校验 getName():当前realm的名称 */ SimpleAuthenticationInfo auth = new SimpleAuthenticationInfo(user, user.getUserPassword(), getName()); return auth; } // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } }
service层
@Override public Result shiroLogin(User user) { // 获取subject对象,表示当前要执行登录认证的对象 Subject subject = SecurityUtils.getSubject(); // 将当前执行登录的邮箱和密码封装成一个token UsernamePasswordToken token = new UsernamePasswordToken(user.getUserEmail(),user.getUserPassword()); try { // subject携带token执行认证 subject.login(token); } catch (UnknownAccountException e) { return Result.error("邮箱不存在"); } catch (IncorrectCredentialsException e) { return Result.error("密码错误"); } catch (UnauthenticatedException e) { return Result.error("未认证"); } return Result.ok("登录成功",subject.getPrincipal());// subject.getPrincipal()获取认证成功后user信息 }
controller层
@PostMapping("/shiroLogin") public Result shiroLogin(@RequestBody User user) { try { return userService.shiroLogin(user); } catch (Exception e) { e.printStackTrace(); return Result.error(e.getMessage()); } }