service运行需要依赖dao对象,如果说IoC容器只是单独给一个service对象而不给dao对象,会出现空指针异常。所以此时IoC容器帮我们将有关系的Bean绑定好了创建。
注意事项:bean定义时id属性在同一个上下文中(配置文件)不能重复
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- bean标签表示配置bean-->
<!-- id属性是给bean起名字-->
<!-- class属性代表bean的定义类型-->
<bean id="bookDao" class="com.rose.dao.BookDaoImpl"/>
<bean id="bookService" class="com.rose.service.BookServiceImpl"/>
</beans>
ApplicationContect是一个接口不能实例化
public class App {
public static void main(String[] args) {
//获取 IoC容器
ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml");
//获取 bean
BookService bookService=(BookService) context.getBean("bookService");
bookService.save();
}
}
在BookServiceImpl类中,为BookDao提供setter方法
public class BookServiceImpl implements BookService{
//删除业务层使用new方式创建dao对象
private BookDao bookDao;
@Override
public void save() {
System.out.println("book service save...");
bookDao.save();
}
//提供对应set方法注入dao对象
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- bean标签表示配置bean-->
<!-- id属性是给bean起名字-->
<!-- class代表bean的定义类型-->
<bean id="bookDao" class="com.rose.dao.BookDaoImpl"/>
<bean id="bookService" class="com.rose.service.BookServiceImpl">
<!-- 配置service和dao的关系-->
<!-- name属性表示配置那一个具体属性-->
<!-- ref表示参考哪一个bean-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
虽然id和ref都是bookDao,name指的是属性名称,ref是需要依赖注入的bean
public class BookDaoImpl implements BookDao{
public BookDaoImpl(){
System.out.println("book dao constructor is running...");
}
@Override
public void save() {
System.out.println("book dao save... ");
}
}
public interface OrderDao {
void save();
}
public class OrderDaoImpl implements OrderDao{
@Override
public void save() {
System.out.println("order dao save ...");
}
}
//配置信息
<!-- 使用工厂方法实例化Bean-->
<bean id="orderDao" class="com.rose.factory.OrderDaoFactory" factory-method="getOrderDao">
</bean>
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
在spring的配置文件中添加以下内容
<bean id="userFactory" class="com.rose.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
public class BookDaoImpl implements BookDao{
private int connectionNum;
private String baseName;
public void setBaseName(String baseName) {
this.baseName = baseName;
}
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
@Override
public void save() {
System.out.println("book dao save... "+connectionNum+baseNum);
}
}
//配置信息
<bean id="bookDao" class="com.rose.dao.BookDaoImpl">
<property name="connectionNum" value="10"/>
<property name="baseName" value="mysql"/>
</bean>
public class BookServiceImpl implements BookService{
private BookDao bookDao;
private UserDao userDao;
public BookServiceImpl(BookDao bookDao, UserDao userDao) {
this.bookDao = bookDao;
this.userDao = userDao;
}
@Override
public void save() {
System.out.println("book service save...");
bookDao.save();
userDao.save();
}
//配置信息
<bean id="bookDao" class="com.rose.dao.BookDaoImpl"/>
<bean id="userDao" class="com.rose.dao.UserDaoImpl"/>
<bean id="bookService" class="com.rose.service.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="需要扫描的包的路径"/>
</beans>
@Component注解不可以添加在接口上,因为接口是无法创建对象的。
@Configuration //配置类
@ComponentScan("com.rose.dao")//包扫描配置信息
public class SpringConfig {
}
Spring为了使用注解简化开发,并没有提供构造函数注入、setter注入对应的注解,只提供了自动装配的注解实现
@Autowired可以写在属性上,也可也写在setter方法上,最简单的处理方式是写在属性上并将setter方法省略
自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值,普通反射只能获取public修饰的内容,暴力反射除了获取public修饰的内容还可以获取private修改的内容,所以此处无需提供setter方法
@Bean注解的作用是将方法的返回值制作为Spring管理的一个bean对象,第三方配置类不用加@Configuration注解
@Configuration //核心配置类
@Import(JdbcConfig.class)//导入外部配置类
@ComponentScan({"com.rose.dao","com.rose.service"})
public class SpringConfig {
}
public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://127.0.0.1:3306/spring_db")
private String url;
@Value("root")
private String username;
@Value("root")
private String password;
@Bean
//该方法的返回值就是要创建的Bean对象类型
public DataSource dataSource(){
DruidDataSource dataSource=new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
AOP(Aspect Oriented Programming)面向切面编程:在不惊动原始设计的基础上(不用修改源代码)为其进行功能增强。Spring的AOP是对一个类的方法在不进行任何修改源代码的前提下实现增强
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
//记录程序当前执行执行(开始时间)
Long startTime = System.currentTimeMillis();
//业务执行万次
for (int i = 0;i<10000;i++) {
System.out.println("book dao save ...");
}
//记录程序当前执行时间(结束时间)
Long endTime = System.currentTimeMillis();
//计算时间差
Long totalTime = endTime-startTime;
//输出信息
System.out.println("执行万次消耗时间:" + totalTime + "ms");
}
public void update(){
System.out.println("book dao update ...");
}
public void delete(){
System.out.println("book dao delete ...");
}
public void select(){
System.out.println("book dao select ...");
}
}
只有save方法中存在打印万次和记录时间的代码,但是在App容器中获取BookDao对象之后调用四个方法,会发现update和delete方法中也执行了打印万次和记录时间的功能。这个案例中其实就使用了Spring的AOP,在不惊动(改动)原有设计(代码)的前提下,想给谁添加功能就给谁添加
实现原理:将其它方法想要增强的功能单独写成一个方法。
连接点
对于上面的案例中BookServiceImpl中有save,update,delete和select方法,这些方法就叫连接点
在BookServiceImpl的四个方法中,update和delete只有打印没有计算万次执行消耗时间,但是在运行的时候已经有该功能,那也就是说update和delete方法都已经被增强,所以对于需要增强的方法就叫切入点
执行BookServiceImpl的update和delete方法的时候都被添加了一个计算万次执行消耗时间的功能,将这个功能抽取到一个方法中,换句话说就是存放共性功能的方法叫通知
通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,哪个切入点需要添加哪个通知,就需要提前将它们之间的关系描述清楚,那么对于通知和切入点之间的关系描述就叫切面
通知是一个方法,方法不能独立存在需要被写在一个类中,这个类我们也就叫通知类
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
@Repository
public class BookDaoImpl implements BookDao{
@Override
public void save() {
//打印当前系统时间
System.out.println(System.currentTimeMillis());
System.out.println(" book dao save ...");
}
@Override
public void update() {
System.out.println(" book dao update ...");
}
}
切入点定义依托一个不具有实际意义的方法进行,无参数,无返回值
6. 制作切面,绑定切入点与通知的关系
7. 将通知类配给容器,并标识为切面类
8. Spring开启AOP功能
AOP是基于Spring容器管理的bean做的增强,核心本质:代理模式
不能直接打印对象,从上面两次结果中可以看出,直接打印对象走的是对象的toString方法,不管是不是代理对象打印的结果都是一样的,原因是内部对toString方法进行了重写。
切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述方式
描述方法无论是用接口的方法还是实现类的方法都可以,推荐使用接口
" * "和 " … "两个符号放在方法参数有区别,前者表示有单个任意独立的参数,后者表示多个或者一个任意参数
@Repository
public class BookDaoImpl implements BookDao{
@Override
public int select() {
System.out.println(" book dao select is running ...");
return 100;
}
@Override
public void update() {
System.out.println(" book dao update is running ...");
}
}
@Component//加载到Spring容器
@Aspect//当作AOP处理而不是Bean
public class MyAdvice {
//定义切入点
@Pointcut("execution(void com.rose.dao.BookDao.update())")
private void pt(){}
//通知
//绑定切入点和通知类的方法
@Before("pt()")//将增强的方法写到原始方法的最前面
public void before(){
System.out.println(" before advice ...");
}
}
@After("pt()")
public void after(){
System.out.println(" after advice ...");
}
环绕通知需要调用原始方法,并且调用原始方法时需要抛出异常
@Around("pt()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(" before around advice ..." );
pjp.proceed();
System.out.println(" after around advice ..." );
}
使用环绕通知的话,要根据原始方法的返回值来设置环绕通知的返回值。返回的是Object而不是int的主要原因是Object类型更通用
//定义新的切入点
@Pointcut("execution(int com.rose.dao.BookDao.select())")
private void pt2(){}
@Around("pt2()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(" before around advice ..." );
Object ret=pjp.proceed();
System.out.println(" after around advice ..." );
//可以篡改原始方法返回值
//return 200
return ret;
}
@Component
@Aspect
public class ProjectAdvice {
//定义切入点
//匹配业务层所有方法
@Pointcut("execution(* com.rose.service.*Service.*(..))")
private void pt(){}
//需要增强的功能
@Around("ProjectAdvice.pt()")//使用的是环绕通知,在原始方法前后增加功能
public void serviceSpeed(ProceedingJoinPoint pjp) throws Throwable {
//记录开始时间
long start=System.currentTimeMillis();
for (int i = 0; i <10000 ; i++) {
//调用原始方法
pjp.proceed();
}
//记录结束时间
long end=System.currentTimeMillis();
System.out.println("业务层接口万次执行时间: "+(end-start)+"ms");
}
}
@Component
@Aspect
public class ProjectAdvice {
//定义切入点
//匹配业务层所有方法
@Pointcut("execution(* com.rose.service.*Service.*(..))")
private void pt(){}
//需要增强的功能
@Around("ProjectAdvice.pt()")//使用的是环绕通知,在原始方法前后增加功能
public void serviceSpeed(ProceedingJoinPoint pjp) throws Throwable {
//获取执行签名信息
//封装了一次执行过程
Signature signature = pjp.getSignature();
//通过签名获取执行操作名称(接口名)
String className = signature.getDeclaringTypeName();
//通过签名获取执行操作名称(方法名)
String methodName = signature.getName();
//记录开始时间
long start=System.currentTimeMillis();
for (int i = 0; i <10000 ; i++) {
//调用原始方法
pjp.proceed();
}
//记录结束时间
long end=System.currentTimeMillis();
System.out.println("万次执行时间: "+className+"."+methodName+"---->"+(end-start)+"ms");
}
}
非环绕通知获取方式是在方法上添加JoinPoint,通过JoinPoint来获取参数
获取参数的结果使用的对象数组,因为方法中参数可能不唯一
使用JoinPoint的方式获取参数适用于前置、后置、返回后、抛出异常后通知。
环绕通知使用的是ProceedingJoinPoint,因为ProceedingJoinPoint是JoinPoint类的子类,所以对于JoinPoint类中所有的方法会被继承,例如getArgs()方法,pjp可直接使用
只有返回后AfterReturing和环绕Around这两个通知类型可以获取
public interface ResourceService {
public boolean openURL(String url,String password);
}
@Service
public class ResourceServiceImpl implements ResourceService{
@Autowired
private ResourceDao resourceDao;
@Override
public boolean openURL(String url, String password) {
//将数据转调给业务层执行
return resourceDao.readResources(url,password);
}
}
public interface ResourceDao {
boolean readResources(String url,String password);
}
@Repository
public class ResourceDaoImpl implements ResourceDao{
@Override
public boolean readResources(String url, String password) {
//补充一个模拟检验密码的功能
return password.equals("root");
}
}
@Configuration //配置类
@ComponentScan("com.rose")
@EnableAspectJAutoProxy//告诉Spring有使用注解开发的AOP,启动配置中的@Aspect注解
public class SpringConfig {
}
public class AppForAnnotation {
public static void main(String[] args) {
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
ResourceService resourceService=context.getBean(ResourceService.class);
boolean flag=resourceService.openURL("http://pan.baidu.com/xixi","root ");
System.out.println(flag);
}
@Component
@Aspect
public class ProjectAdvice {
//定义切入点
//匹配业务层所有方法,将带空格的参数都去空格操作
@Pointcut("execution(boolean com.rose.dao.*Dao.*(..))")
private void pt() {}
@Around("pt()")
public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
//获取原始方法中的参数
Object[] args = pjp.getArgs();
//去除原始方法传入的字符串参数带有空格的字符串参数
//foreach只能读数据,不可以修改原始数据
for (int i = 0; i <args.length ; i++) {
//判断是不是字符串
if(args[i].getClass().equals(String.class)){
//取出数据后转换类型,在更新数据
args[i] = args[i].toString().trim();
}
}
//将修改后去除空格的参数传入原始方法中
Object ret=pjp.proceed(args);
return ret;
}
}
从名称上可以看出,我们只需要给它一个DataSource对象,它就可以帮你去在业务层管理事务。其内部采用的是JDBC的事务。所以说如果你持久层采用的是JDBC相关的技术,就可以采用这个事务管理器来管理你的事务。而Mybatis内部采用的就是JDBC的事务,所以后期我们Spring整合Mybatis就采用的这个DataSourceTransactionManager事务管理器。
在JDBC的配置类中设定事务管理器
事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
事务协调员:加入事务方,在Spring中通常指代数据层方法CURD,也可以是业务层方法
readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。
rollbackFor:当出现指定异常进行事务回滚
当不出现异常时,转账成功并且日志成功打印;当出现异常时就不会转账也不会打印日志,这是因为日志的记录与转账操作隶属同一个事务,同成功同失败
mandatory:原来操作带事务就可以加入事务,如果不携带事务就调用方法会报错
SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装。
SpringMVC接收到请求和数据后,进行一些处理,当然这个处理可以是转发给Service,Service层再调用Dao层完成的,不管怎样,处理完以后,都需要将结果告知给用户。
@ResponseBody注解激活类型的自动转换,设置当前控制器返回值作为响应体
内部转换原理是一个专门用于 http 消息转换的接口:HttpMessageConvert接口
SpringBoot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化 Spring 应用的初始搭建以及开发过程
Spring官网配置优先级说明
分页查询的返回值是 Page 类型
核心步骤:设置分页拦截器,MybatisPlus中提供了一个拦截器,在这个 拦截器 中添加 分页拦截器 ,只有添加分页拦截器才能显示分页功能。配置类和方法上需要添加注解
<dependencies>
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<!-- mysql 驱动,连接数据库-->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<!-- 单元测试-->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.20</version>
</dependency>
<!-- 添加logback-classic依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 添加logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
mybatis官网链接
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载 sql 映射文件,指定映射文件路径-->
<!-- <mapper resource="org/example/mapper/UserMapper.xml"/>-->
<!-- Mapper 代理方式-->
<package name="org.example.mapper"/>
</mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace:名称空间
id:sql语句的唯一标识,不可以重复
resultType:数据返回结果的类型,本案例中我们需要返回一个User类型,并写出相应的路径
-->
<mapper namespace="test">
<select id="selectAll" resultType="org.example.User">
select * from tb_user;
</select>
</mapper>
public class Main {
//1.加载mybatis核心配置文件, 获取 SQLSessionFactory
public static void main(String[] args) throws IOException {
//获取核心配置文件的路径
String resource = "mybatis-config.xml";
//文件IO 资源加载的类Resource 将文件中的字符串传入,返回一个字节输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSessionFactoryBuilder通过builder方法把流传入就能返回工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.获取SqlSession对象,用来执行sql语句
SqlSession sqlSession = sqlSessionFactory.openSession();
//3.执行sql 参数传入mapper文件中相应的sql语句唯一标识id
List<User> users = sqlSession.selectList("test.selectAll");
System.out.println(users);
//4.释放资源
sqlSession.close();
}
}
public class Main {
//1.加载mybatis核心配置文件, 获取 SQLSessionFactory
public static void main(String[] args) throws IOException {
//获取核心配置文件的路径
String resource = "mybatis-config.xml";
//文件IO 资源加载的类Resource 将文件中的字符串传入,返回一个字节输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSessionFactoryBuilder通过builder方法把流传入就能返回工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.获取SqlSession对象,用来执行sql语句
SqlSession sqlSession = sqlSessionFactory.openSession();
//3.执行sql 获取UserMapper的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
System.out.println(users);
//4.释放资源
sqlSession.close();
}
}
从下面两个例子可以看出,以后开发我们使用 #{} 参数占位符。
status=?:只有两种状态,启用=1,禁用=0
companyName和brandName不用 ? 而用 like 是因为用户并不知道企业名称全称和品牌名字全称,所以使用模糊匹配来包含用户查询输入的信息
Mybatis提供三种多种参数接收方式(重点掌握)
散装参数:使用 @Param注解 让多个参数明确对应的占位符,例如 int status 参数对应的是注解括号内 " status " 占位符。格式 @Param(“SQL参数占位符名称”)
封装对象:将所有参数封装为对象,占位符需要对应参数时,从对象的get方法中获取,这意味着占位符的名称要和对象属性名称相同,不一样就找不到对应的get方法
封装为Map集合:key值保存占位符,value值保存对应的参数
set标签的作用:
1.可以处理逗号的问题(例如用户不想修改status的数据,此时description字段就是最后一个数据并且带逗号,执行方法时有sql语法错误)
2.可以处理什么字段都不修改的情况(用户所有字段都不修改时,sql语句会包含一个set,会造成sql语法错误)
但是 ul 标签一般不赞成使用 type 属性,会用 css 样式取代