SSM
一、Spring
1、简介
Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。
2、特点
1.方便解耦,简化开发
2.AOP编程的支持
3.声明式事务的支持
4.方便程序的测试
5.方便集成各种优秀框架
6.降低Java EE API的使用难度
3、组织架构
ORM- object relation mapping
OXM-Object xml mapping
JMS - Java消息服务(Java Message Service ,JMS)
WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一
开始的握手需要借助HTTP请求完成。Socket是传输控制层协议,WebSocket是应用层协议。
Portlet是一种Web组件-就像servlets-是专为将合成页面里的内容聚集在一起而设计的。通常请求一
个portal页面会引发多个portlets被调用。每个portlet都会生成标记段,并与别的portlets生成的标记段
组合在一起嵌入到portal页面的标记内
spring全家桶:spring,Spring Data、Spring MVC、Spring Boot、Spring Cloud(微服务)
4、核心模块
-spring-core:依赖注入IOC与DI的最基本实现
- spring-beans:Bean工厂与bean的装配
- spring-context:spring的context上下文即IoC容器
- spring-context-support
- spring-expression:spring表达式语言
5、IOC
IOC是 Inverse of Control 的简写,意思是控制反转。是降低对象之间的耦合关系的设计思想。
DI是Dependency Injection的缩写,意思是依赖注入,说的是创建对象实例时,同时为这个对象注入它所依赖的属性
实现过程:
1.导入依赖包:
<!-- Spring的核心工具包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--在基础IOC功能上提供扩展服务,还提供许多企业级服务的支持,有邮件服务、 任务调度、远程访问、缓存以及多种视图层框架的支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring IOC的基础实现,包含访问配置文件、创建和管理bean等 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring context的扩展支持,用于MVC方面 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring表达式语言 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
2.在resource下面创建配置文件:application.xml
<?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">
</beans>
3.在配置文件中创建对象
<bean id="对象名" class="类的完整路径">
<property name="属性名" ref="对象的id值"></property>
</bean>
4.加载配置文件,获取对象
ApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
Users users=(Users)app.getBean("u1");
bean标签的属性:
对象的创建方式:
(1)无参构造
(2)有参构造
public Person(String name , Car car){
this.name = name;
this.car = car;
System.out.println("Person的有参构造方法:"+name+car);
}
<bean name="person" class="com.java.spring.bean.Person">
<constructor-arg name="name" value="rose"/> <constructor-arg name="car"ref="car"/> </bean>
(3)静态方法创建对象
public class PersonFactory {
public static Person createPerson(){
System.out.println("静态工厂创建Person");
return new Person();
}
}
<!--factorymethod指的是类中的方法名-->
<bean name="pf" class="com.java.PersonFactory" factory-method="createPerson" />
(4)非静态工厂方法
public class Users{
public Person createPerson1(){
System.out.println("非静态工厂创建Person");
return new Person();
}
}
<!--factorymethod指的是类中的方法名-->
<bean id="u2" class="com.java.bean.Users"></bean>
<bean id="u3" factorymethod="createPerson1" factory-bean="u2"></bean>
SpringBean的生命周期
1)根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean。
2)利用依赖注入完成 Bean 中所有属性值的配置注入。
3)如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前
Bean 的 id 值。
4)如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂
实例的引用。
5)如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
6)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它
实现的。
7)如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。初始化bean的时候执行,可以针对某个具体的bean进行配置。afterPropertiesSet 必须实现 InitializingBean
接口。实现 InitializingBean接口必须实现afterPropertiesSet方法。
8)如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
9)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
10)如果在 中指定了该 Bean 的作用范围为 scope="singleton",则将该 Bean 放入 Spring IoC 的缓
存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用范围为
scope="prototype",则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该
Bean。
11)如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方
法对 Bean 进行销毁。
6、DI
分类:一种是调取属性的set方法赋值,第二种使用构造方法
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--DI赋值-->
<bean id="stu1" class="com.java.bean.Student">
<!--基本属性赋值-->
<property name="stuname" value="张三"></property>
</bean>
<!--引用属性赋值-->
<bean id="stu" class="com.java.bean.Student">
<property name="stuname" ref="stu1"></property>
</bean>
<!--通过name属性,按照参数名赋值-->
<bean id="stu2" class="com.java.bean.Student">
<constructor-arg name="sex" value="男" ></constructor-arg>
<constructor-arg name="age" value="19" ></constructor-arg>
<!--spel spring表达式赋值-->
<constructor-arg name="stuname" value="#{stu1.stuname}"></constructor-arg>
</bean>
<!--P命名空间注入值-->
<bean id="stu3" class="com.xzk.bean.Student" p:stuname="aa" p:age="18" p:sex="女" ></bean>
</beans>
注意:P命名空间注入值:
基本类型值:
p:属性名=“值”- 引用类型值:
P:属性名-ref=“bean名称”
实现步骤:配置文件中 添加命名空间p
xmlns:p="http://www.springframework.org/schema/p"
复杂类型的赋值:
<?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 id="t1" class="com.java.bean.Teacher">
<!--数组类型赋值-->
<property name="obj">
<list>
<value>张三</value>
<value>12313</value>
<value>qsghs</value>
<!-- <ref bean="u2"></ref>-->
</list>
</property>
<!--List类型赋值-->
<property name="list">
<list>
<value>张三1</value>
<value>123</value>
<value>qss</value>
<!-- <ref bean="u2"></ref>-->
</list>
</property>
<!--Set类型-->
<property name="set">
<set>
<value>张三2</value>
<value>122</value>
<value>qsa</value>
<!-- <ref bean="u2"></ref>-->
</set>
</property>
<!--Map类型赋值-->
<property name="map">
<map>
<entry key="username" value="李四"></entry>
<entry key="age" value="8855"></entry>
<!-- <entry key="user" value-ref="u1"></entry>-->
</map>
</property>
<!--属性类型赋值-->
<property name="properties">
<props>
<prop key="username">王五</prop>
<prop key="password">145632</prop>
</props>
</property>
</bean>
</beans>
获取值:
public static void main(String[] args) {
//1.加载配置文件
ApplicationContext app=new ClassPathXmlApplicationContext("application.xml");
Teacher t=(Teacher) app.getBean("t1");
//2.得到对象中的信息
System.out.println("--------------数组------------");
Object[] objects = t.getObj();
for (Object object : objects) {
System.out.println(object);
}
System.out.println("--------------List------------");
List list = t.getList();
for (Object o : list) {
System.out.println(o);
}
System.out.println("--------------Set---------------");
Set set = t.getSet();
for (Object o : set) {
System.out.println(o);
}
System.out.println("-----------------Map--------------");
Map map = t.getMap();
Set keySet = map.keySet();
Iterator iterator = keySet.iterator();
while(iterator.hasNext()){
String key=(String) iterator.next();
System.out.println(key+","+map.get(key));
}
System.out.println("----------Properties-----------");
Properties properties = t.getProperties();
System.out.println(properties.getProperty("username"));
System.out.println(properties.getProperty("password"));
}
7、自动注入
autowire:
no 不自动装配(默认值)
byName 属性名=id名 ,调取set方法赋值
byType 属性的类型和id对象的类型相同,当找到多个同类型的对象时报错,调取set方法赋值
constructor 构造方法的参数类型和id对象的类型相同,当没有找到时,报错。调取构造方法赋值
示例:
<bean id="service" class="service.impl.UserServiceImpl" autowire="constructor"> </bean>
配置全局自动装配:
<beans default-autowire="constructor/byName/byType/no">
8、注解实现IOC
1、添加依赖:
pom.xml中:
spring-framework-5.0.8.RELEASE\docs\spring-framework-
reference\html\xsdconfiguration.html
application.xml中:
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
2、配置注解扫描:指定扫描包下所有类中的注解,扫描包时,会扫描包所有的子孙包:
<!--扫描包设置-->
<context:component-scan base-package="com.java"></context:component- scan>
3、注解:
- 注解在类名上:
@Component("对象名")
@Service("person") // service层
@Controller("person") // controller层
@Repository("person") // dao层
@Scope(scopeName="singleton") //单例对象
@Scope(scopeName="prototype") //多例对象
- 注解在属性上:
@Value("属性值")
private String name;
@Autowired //如果一个接口类型,同时有两个实现类,则报错,此时可以借助@Qualifier("bean name")
@Qualifier("bean name")
private Car car;
//说明:@Resource 是java的注释,但是Spring框架支持,@Resource指定注入哪个名称的对象 //@Resource(name="对象名") == @Autowired + @Qualifier("name")
@Resource(name="baoma")
private Car car;
- 注解在方法上
@PostConstruct //等价于init-method属性
public void init(){
System.out.println("初始化方法");
}
@PreDestroy //等价于destroy-method属性
public void destroy(){
System.out.println("销毁方法");
}
9、AOP
AOP(Aspect Oriented Programming)即面向切面编程。即在不改变原程序的基础上为代码段增加新
的功能。应用在权限认证、日志、事务。
JDK代理模式:
针对实现了接口的类产生代理。InvocationHandler接口。
- 创建接口和对应实现类
public interface UserService {
public void login();
}
//实现类
public class UserServiceImpl implements UserService {
public void login(){
System.out.println("UsersDaoImpl");
}
}
- 创建动态代理类,实现InvocationHandler接口
public class agency implements InvocationHandler {
private UserService target; //目标对象
public agency(UserService target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//本方法中的其他输出输入增强
//proxy 代理方法被调用的代理实例
System.out.println("方法触发了");
//执行被代理类 原方法
Object invoke = method.invoke(target, args);
System.out.println("执行完毕了");
return invoke;
}
}
- 测试
//测试JDK动态代理技术
UserService us = new UserServiceImpl();
agency ag = new agency(us);
//这里不能转换成一个实际的类,必须是接口类型
UserService uservice = (UserService)Proxy.newProxyInstance(us.getClass().getClassLoader(),us.getClass().getInterfaces(),ag);
uservice.login();
CGlib代理模式:
针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术 生成当前类的子类对象,MethodInterceptor接口
- 导入依赖包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency
- 创建实体类
public class Users{ public void login(){} }
- 创建CGlib代理对象
class CgProxy implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("输出语句1");
//参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法 //引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
Object obj= methodProxy.invokeSuper(o,objects);
System.out.println("输出语句2");
return obj;
}
}
- 测试
public static void main(String[] args) {
//1.创建真实对象
Users users = new Users();
//2.创建代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(users.getClass());
enhancer.setCallback(new CglibProxy());
Users o = (Users) enhancer.create();//代理对象
o.login();
}
区别:
1、jdk动态代理生成的代理类和委托类实现了相同的接口;
2、cglib动态代理中生成的字节码更加复杂,生成的代理类是委托类的子类,且不能处理被fifinal关键字
修饰的方法;
3、jdk采用反射机制调用委托类的方法,cglib采用类似索引的方式直接调用委托类方法;
Spring中使用AOP
1.导入依赖包
<dependencies>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
</dependencies>
2.创建接口和实现类:
//User dao层接口和实现类
public interface UserDao {
public void test1();
}
public class UserDaoImpl implements UserDao {
public void test1() {
System.out.println("userTest1------");
}
}
//User service层接口和实现类
public interface UserService {
public void test1();
}
public class UserServiceImpl implements UserService {
private UserDao dao;
public void setDao(UserDao dao) {
this.dao = dao;
}
public void test1() {
System.out.println("userserviceTest1-----");
dao.test1();
}
}
//创建Aop的增强类
public class MyAop {
//前置增强,读取目标方法之前执行
public void before(){
System.out.println("日志开始-----");
}
//后置增强,读取目标方法之后执行
public void after(){
System.out.println("日志结束-----");
}
}
3.添加application.xml配置文件并且配置对应参数
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean id="udao" class="com.java.dao.impl.UserDaoImpl"></bean>
<bean id="uservice" class="com.java.service.impl.UserServiceImpl">
<property name="dao" ref="udao"></property>
</bean>
<!--创建增强类的对象-->
<bean id="myaop" class="com.java.util.MyAop"></bean>
<!--建立增强类和目标方法之间的关系-->
<aop:config>
<aop:pointcut id="mypc" expression="execution(public void com.java.service.UserService.test1())"></aop:pointcut>
<aop:aspect ref="myaop">
<!--前置增强-->
<aop:before method="before" pointcut-ref="mypc"></aop:before>
<!--后置增强-->
<aop:after-returning method="after" pointcut-ref="mypc"></aop:after-returning>
</aop:aspect>
</aop:config>
</beans>
4.测试
public class Test1 {
public static void main(String[] args) {
//创建对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
UserService userService = (UserService)applicationContext.getBean("uservice");
userService.test1();
}
}
增强类的类型:
//前置通知:目标方法运行之前调用 aop:before
//后置通知(如果出现异常不会调用):在目标方法运行之后调用 aop:after-returning
//环绕通知:在目标方法之前和之后都调用 aop:around
//最终通知(无论是否出现 异常都会调用):在目标方法运行之后调用 aop:after
//异常增强:程序出现异常时执行(要求:程序代码中不要处理异常) aop:after-throwing
表达式定义:
例如:expression=“execution(public void com.java.service.UserService.test1())”
1、public * addUser(com.pb.entity.User):“*”表示匹配所有类型的返回值
2、public void (com.pb.entity.User):“”表示匹配所有方法名。
3、public void addUser (…):“…”表示匹配所有参数个数和类型
4、* com.pb.service..(…):匹配com.pb.service 包下所有类的所有方法
5、* com.pb.service…*(…):匹配com.pb.service 包及子包下所有类的所有方法
获取切点信息:
使用JoinPoint。
public void before(JoinPoint joinPoint){
System.out.println("日志开始-----");
System.out.println(new Date()+"切点对象信息:"+joinPoint.getTarget().getClass().getSimpleName());
System.out.println("方法信息:"+joinPoint.getSignature());
System.out.println("参数信息:"+joinPoint.getArgs());
}
特殊的前置增强Advisor前置增强
- 创建实现类:MethodBeforeAdvice
public class BeforeAop implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("特殊的前置增强");
System.out.println("目标方法:"+method.getName());
}
}
- 修改配置文件
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean id="udao" class="com.java.dao.impl.UserDaoImpl"></bean>
<bean id="uservice" class="com.java.service.impl.UserServiceImpl">
<property name="dao" ref="udao"></property>
</bean>
<!--创建增强类的对象-->
<bean id="myaop" class="com.java.util.MyAop"></bean>
<bean id="before" class="com.java.util.BeforeAop"></bean>
<!--建立增强类和目标方法之间的关系-->
<aop:config>
<aop:pointcut id="mypc" expression="execution(* com.java.service.*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="before" pointcut-ref="mypc"></aop:advisor>
</aop:config>
</beans>
注解方式中注解的顺序问题:
- 1.没有异常情况下
- 环绕开始。。。。
- 前置增强开始执行
- insert-----------
- 环绕结束。。。。
- 最终增强
- 后置增强开始执行
- 相对顺序固定,注解换位置时不影响结果顺序
- 2.有异常
- 前置增强开始执行
- insert-----------
- 最终增强
- 异常增强
- 注意:不要使用环绕增强,使用的话,异常增强不执行
注意:
aop的应用场景:事务底层实现,日志,权限控制,mybatis中sql绑定,性能检测
10、Spring连接JDBC
- 导入依赖包
Mysql 5版本:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
</dependencies>
测试:
public static void main(String[] args) throws PropertyVetoException {
//创建数据库连接池对象
ComboPooledDataSource dataSource=new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql//localhost:3306/user?userUnicode=true&characterEncoding=utf-8");
dataSource.setUser("root");
dataSource.setPassword("123456");
//创建工具类
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//调用方法,增删改查用update 查询用query方法
int update = jdbcTemplate.update("insert into student values(1,'stu11','haha')");
System.out.println(update);
}
11、事务管理
通过sql将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性(准确性)。
事务通常是以begin transaction开始,以commit或rollback结束。
事务执行的流程:开启事务->执行insert,update,delete->commit/rollback
原因:
(1)为了提高性能
(2)为了保持业务流程的完整性
(3)使用分布式事务
特性:
1 - 原子性(atomicity)
事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。
2、一致性(consistency)
事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。
3、隔离性(isolation)
一个事务的执行不能被其他事务所影响。企业级的数据库每一秒钟都可能应付成千上万的并发访问,因而带来了并发控制的问题。
4、持久性(durability)
一个事务一旦提交,事务的操作便永久性的保存在DB中。即使此时再执行回滚操作也不能撤消所做的更改。
注意:
spring事务是基于代理来实现的,所以:
(1)private、fifinal、static 方法无法被代理,所以添加事务无效。
(2)当绕过代理对象, 直接调用添加事务管理的方法时, 事务管理将无法生效。比如直接new出的对象。
(3)在同一个类下,有2个方法,A、B,A没有事务,B有事务,但是A调用B时,方法B被标记的事务无效。 究其原因,因为此类的调用对象为代理对象,代理方法A调用真正的被代理方法A后,在被代理方法A中才会去调用方法B,此时this对象为被代理的对象,所以是不会通知到代理对象,也就变成了第二种情况,绕过了代理对象。所以无效。
隔离级别:
-
Serializable(串行化):可避免脏读、不可重复读,幻读情况的发生。
-
Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。
-
Read committed(读已提交):可避免脏读情况发生。
-
Read uncommitted(读未提交):最低级别,以上情况均无法保证。
Spring实现事务管理:
添加依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
事务属性介绍:
package org.springframework.transaction;
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0; //支持当前事务,如果不存在,就新建一个
int PROPAGATION_SUPPORTS = 1; //支持当前事务,如果不存在,就不使用事务
int PROPAGATION_MANDATORY = 2; //支持当前事务,如果不存在,就抛出异常
int PROPAGATION_REQUIRES_NEW = 3;//如果有事务存在,挂起当前事务,创建一个新的事物
int PROPAGATION_NOT_SUPPORTED = 4;//以非事务方式运行,如果有事务存在,挂起当前事务
int PROPAGATION_NEVER = 5;//以非事务方式运行,如果有事务存在,就抛出异常
int PROPAGATION_NESTED = 6;//如果有事务存在,则嵌套事务执行
int ISOLATION_DEFAULT = -1;//默认级别,MYSQL: 默认为REPEATABLE_READ级别 SQLSERVER: 默认为READ_COMMITTED
int ISOLATION_READ_UNCOMMITTED = 1;//读取未提交数据(会出现脏读, 不可重复读) 基本不使用
int ISOLATION_READ_COMMITTED = 2;//读取已提交数据(会出现不可重复读和幻读)
int ISOLATION_REPEATABLE_READ = 4;//可重复读(会出现幻读)
int ISOLATION_SERIALIZABLE = 8;//串行化
int TIMEOUT_DEFAULT = -1;//默认是-1,不超时,单位是秒
//事务的传播行为
int getPropagationBehavior();
//事务的隔离级别
int getIsolationLevel();
//事务超时时间
int getTimeout();
//是否只读
boolean isReadOnly();
String getName(); }
添加配置:
<!-- 平台事务管理器 -->
<bean id="transactionManager class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为:propagation 不是必须的,默认值是REQUIRED -->
<!-- REQUIRED:如果有事务,则在事务中执行;如果没有事务,则开启一个新的事务 -->
<tx:method name="save*" propagation="REQUIRED" />
<!-- SUPPORTS:如果有事务,则在事务中执行;如果没有事务,则不会开启事务 -->
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice> <aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.service.*.* (..))" /><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config>
二、Spring MVC
1、Web请求过程
2、核心组件
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合 性,提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式 等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器,调用处理器传递参数等工作!
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等
3、Spring Mvc执行过程
4、框架搭建
1、创建项目:webapp。
2、在main目录下面新建java和resources目录,并且编译
java目录:
resources目录:
3、添加依赖包
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
4、修改web.xml文件
<servlet>
<servlet-name>aa</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>aa</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
注意:/和/*的区别
< url-pattern > / </ url-pattern > 不会匹配到*.jsp,即:*.jsp不会进入spring的 DispatcherServlet类 。
< url-pattern > /* </ url-pattern > 会匹配*.jsp,会出现返回jsp视图时再次进入spring的DispatcherServlet 类, 导致找不到对应的controller所以报404错。
可以配置/ ,此工程 所有请求全部由springmvc解析,此种方式可以实现 RESTful方式,需要特殊处理对静态文件 的解析不能由springmvc解析
可以配置*.do*或.action,所有请求的url扩展名为.do或.action由springmvc解析,此种方法常用
不可以/,如果配置/,返回jsp也由springmvc解析,这是不对的。
5、修改spring配置文件
<?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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd" >
<!--1、扫描注解包-->
<context:component-scan base-package="com.java"></context:component-scan>
<!--2、创建视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--用来指定结果页面的前缀和后缀-->
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
6、创建控制器类
1.@Controller
2.@RequestMapping(“请求地址”)
* 加在类上: 给模块添加根路径
* 加载方法: 方法具体的路径
设置@RequestMapping method属性
@RequestMapping(method=RequestMethod.GET,value=“请求名”)
@Controller
public class MyController {
@RequestMapping("/test")
public String test1(){
System.out.println("test1被执行了-----");
//return 的是结果页面的名称
// -->因为在springmvc.xml配置文件中制定了前缀和后缀,所以最后呈现的是/success.jsp
return "success";
}
}
执行过程:首先访问index.jsp --> 访问web.xml -->跳转DispatcherServlet和springmvc.xml -->访问Mycontroller -->访问success.jsp
5、获取前端的数据
(1)HttpServletRequest
(2)页面传值时的key=处理请求的方法的参数名
<html>
<body>
<h2>Hello World!</h2>
<a href="/test?username=yyy&age=10">test</a>
</body>
</html>
//其中username和age必须和前端的命名是一样的
@RequestMapping("/test")
public String test1(String username,int age){
System.out.println("test1被执行了-----");
System.out.println("username:"+username);
System.out.println("age:"+age);
//return 的是结果页面的名称
// -->因为在springmvc.xml配置文件中制定了前缀和后缀,所以最后呈现的是/success.jsp
return "success";
}
(3)使用控件名和对象的属性名一致的方式进行接收
创建一个实体类,其属性就是前端需要传输的值,通过调用实体类来进行传值。
//实体类
public class User {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//传值
@RequestMapping("/test")
public String test1(User user){
System.out.println("test1被执行了-----User");
System.out.println("username:"+user.getUsername());
System.out.println("age:"+user.getAge());
//return 的是结果页面的名称
// -->因为在springmvc.xml配置文件中制定了前缀和后缀,所以最后呈现的是/success.jsp
return "success";
}
如果key值和参数名不同:使用@RequestParam(value = “”)进行指定对应关系
public String login(@RequestParam(value = "name") String username, String password){} //设置默认值
public String list(@RequestParam(defaultValue = "1") Integer currentPage)
如果key值和参数名的类型不同会导致无法执行:
**日期问题处理:**springmvc框架默认支持转换得日期格式:yyyy/MM/dd
(1)使用string接受日期,接受后,再转换: SimpleDataFormate
(2)使用工具类处理日期
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
</dependency>
配置文件:<mvc:annotation-driven/>
public String test1(@DateTimeFormat(pattern = "yyyy-MM-dd")Date birthday){}
6、返参
修改web.xml以支持jsp的EL表达式
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
(1)HttpServletRequest
(2)ModelMap map ,默认作用域request
(3)ModelAndView 对象需要new,同时作为返回值类型
(4)Model类保存数据
7、处理session数据
(1)使用HttpSession :request.getSession();
(2)使用@sessionAttributes("key值")//写的是ModelMap中定义的key值
注:该注解和ModelMap结合使用,当使用ModelMap存值时,会在session中同时存储一份数据
@SessionAttributes()的小括号中如果是一个值,不要加{}
示例:
@SessionAttributes("key")
@SessionAttributes({"key1","key2"})
清除注解session:SessionStatus类
status.setComplete();
8、弹窗响应
后端操作:
乱码处理:
转发和重定向:
默认是转发:
@RequestMapping("/forwardView")
public String forwardView(){
return "forward:/WEB_INF/pages/success.jsp";
}
重定向:
//重定向时会忽视视图解析器的配置
return "redirect:a.jsp" 或者:redirect:findall
9、异常处理
在web.xml中单独加一个异常处理页面,然后跳转
//异常处理
@ExceptionHandler(Exception.class)
public String error(){
System.out.println("Error-----");
return "error";
}
设置全局异常处理
//新建一个异常处理类,在类名前面加上@ControllerAdvice注解
@ControllerAdvice
public class ExceptionUtil {
//全局异常处理
@ExceptionHandler(Exception.class)
public String error(){
System.out.println("Error-----");
return "error";
}
}
10、cookie和请求头数据获取
cookie:使用@CookieValue
请求头:使用@RequestHeader
public ModelAndView test2(@CookieValue("JSESSIONID") String sessionid,@RequestHeader("Accept-Language")String language){
System.out.println("sessionid:"+sessionid);
System.out.println("language:"+language);
}
11、RestFul请求模式
REST:即Representational State Transfer ,(资源)表现层状态转化,是目前最流行的一种互联网软件架构。 具体说,就是HTTP协议里面,四个表示操作方式的动词: GET POST PUT DELETE
分别代表着四种基本操作:
- GET用来获取资源
- POST用来创建新资源
- PUT用来更新资源
- DELETE用来删除资源
实现:
1、 web.xml添加HiddenHttpMethodFilter配置
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>aa</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>aa</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>hid</filter-name>
<filter-class>
org.springframework.web.filter.HiddenHttpMethodFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>hid</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
2、新建JSP文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Restful</title>
</head>
<body>
<form action="/testrest" method="post">
<input type="submit" value="post">
</form>
<form action="/testrest" method="get">
<input type="submit" value="get">
</form>
<form action="/testrest" method="post">
<input type="hidden" name="_method" value="put">
<input type="submit" value="put">
</form>
<form action="/testrest" method="post">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="delete">
</form>
</body>
</html>
注意:因为put和delete请求是不存在的,所以需要在其中加入:
<input type="hidden" name="_method" value="delete">
其中form表单的method必须使用post并且input的name必须是_method.
3、测试
@Controller
public class RestController {
@RequestMapping(value = "/testrest",method = RequestMethod.POST)
public String post(){
System.out.println("post请求");
return "postsuccess";
}
@RequestMapping(value = "/testrest",method = RequestMethod.GET)
public String get(){
System.out.println("get请求");
return "getsuccess";
}
@RequestMapping(value = "/testrest",method = RequestMethod.PUT)
public String put(){
System.out.println("put请求");
return "putsuccess";
}
@RequestMapping(value = "/testrest",method = RequestMethod.DELETE)
public String del(){
System.out.println("del请求");
return "delsuccess";
}
}
传值:
使用@PathVariable注解,并且需要在@RequestMapping中设置需要获取的几个变量值,使用{}。
@Controller
public class RestController {
@RequestMapping(value = "/testrest/{name}/{age}",method = RequestMethod.POST)
public String post(@PathVariable("name")String name,@PathVariable("age")int age){
System.out.println("name:"+name+",age:"+age);
System.out.println("post请求");
return "postsuccess";
}
@RequestMapping(value = "/testrest/{name}/{age}",method = RequestMethod.GET)
public String get(@PathVariable("name")String name,@PathVariable("age")int age){
System.out.println("name:"+name+",age:"+age);
System.out.println("get请求");
return "getsuccess";
}
@RequestMapping(value = "/testrest/{name}/{age}",method = RequestMethod.PUT)
public String put(@PathVariable("name")String name,@PathVariable("age")int age){
System.out.println("name:"+name+",age:"+age);
System.out.println("put请求");
return "putsuccess";
}
@RequestMapping(value = "/testrest/{name}/{age}",method = RequestMethod.DELETE)
public String del(@PathVariable("name")String name,@PathVariable("age")int age){
System.out.println("name:"+name+",age:"+age);
System.out.println("del请求");
return "delsuccess";
}
}
12、静态资源访问
1、修改spring.xml
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd ">
2、 添加处理标签
<mvc:annotation-driven /> <!--注解驱动-->
<mvc:resources mapping="/img/**" location="/images/" ></mvc:resources>
方式二:
<mvc:default-servlet-handler></mvc:default-servlet-handler>
13、Json数据处理
1、添加jar包
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
2、实现代码:@ResponseBody 注意:需要在配置文件添加 mvc:annotation-driven/
14、SpringMVC拦截器
1、创建拦截器类:实现HandlerInterceptor接口
preHandle() 拦截器开始
postHandle() 拦截器结束
afterCompletion 最后执行
2、配置拦截器
<!--拦截所有-->
<mvc:interceptors>
<bean id="my" class="util.MyInterceptor"/>
</mvc:interceptors>
<!--拦截指定请求-->
<mvc:interceptors>
<mvc:interceptor >
<mvc:mapping path="/请求名" />
<mvc:mapping path="/请求名" />
<bean id="my" class="util.MyInterceptor"/>
</mvc:interceptor>
</mvc:iterceptors>
使用场景:
1、日志记录 :记录请求信息的日志
2、权限检查,如登录检查
3、性能检测:检测方法的执行时间
过滤器和拦截器的区别:
- 过滤器:
- 依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容 器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,比如:在过滤器中修 改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等
- 拦截器:
- 依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程 (AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操 作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦 截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理 。
- 多个过滤器与拦截器的代码执行顺序
- 1、过滤器的运行是依赖于servlet容器的,跟springmvc等框架并没有关系。并且,多个过滤器的执行顺序跟xml文 件中定义的先后关系有关
- 2、对于多个拦截器它们之间的执行顺序跟在SpringMVC的配置文件中定义的先后顺序有关
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ApVu6ip1-1605966060603)(F:\开课吧-Java\笔记\图片\第十二章\18.png)]
15、文件上传下载
上传:MultipartResolver
1、添加jar包
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
2、配置MultipartResolver:
<mvc:annotation-driven/>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:defaultEncoding="UTF-8" p:maxUploadSize="5242880" /
3、页面表单,提交方式必须是post
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>上传</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
文件:<input type="file" name="myfile">
<input type="submit" value="上传">
</form>
</body>
</html>
4、java代码
@Controller
public class UploadController {
@RequestMapping("/upload")
public String upload(MultipartFile myfile, HttpServletRequest request){
//处理上传的文件内容
//1.将上传的文件夹转换成服务器路径
String realPath = request.getRealPath("/uploadimg");
System.out.println("realpath="+realPath);
//2.得到上传的文件名
String filename = myfile.getOriginalFilename();
//3.上传
try {
myfile.transferTo(new File(realPath+"/"+filename));
} catch (IOException e) {
e.printStackTrace();
}
request.setAttribute("filename",filename);
return "uploadsuccess";
}
}
下载:
1、添加jar包
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
2、处理类
@Controller
public class DownloadController {
@RequestMapping("/download")
public ResponseEntity<byte[]> down(String filename, HttpServletRequest request) throws UnsupportedEncodingException, IOException {
//1.转换服务器地址
String realPath = request.getRealPath("/uploadimg");
//2.得到要下载的文件路径
String filePath= realPath+"/"+filename;
//3.设置响应的头信息
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//给用户弹窗的方式进行下载
//attachment 用来表示以附件的形式响应给客户端
httpHeaders.setContentDispositionFormData("attachment", URLEncoder.encode(filename,"UTF-8"));
//4.创建文件
File file = new File(filePath);
//5.将文件进行返回
ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),httpHeaders, HttpStatus.CREATED);
return responseEntity;
}
}
三、Mybatis
1、简介
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口 和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
2、搭建Mybatis
mysql-connector-java-8.0.21.jar
1、添加依赖包和配置相应的xml文件
<!--pom.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.java</groupId>
<artifactId>MybatisDemo1</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
</dependencies>
</project>
<!--Mybatis.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">
<!--连接数据库的环境,default=环境的id-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--<property name="url" value="jdbc:mysql://localhost:3306/test"/>-->
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT&nullCatalogMeansCurrent = true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 指定maper文件的路径(maven项目从resources源文件夹下找资源)-->
<mappers>
<!-- <mapper resource="包名/mapper文件名"/>-->
<mapper resource="userMapper.xml"></mapper>
</mappers>
</configuration>
2、创建实体类和接口
//实体类
package com.java.bean;
/**
* @Author: YNB
* @Description:
* @Date Created in 2020-11-21 11:18
* @Modified By:
*/
public class User {
private int userid;
private String username;
private String userphone;
public int getUserid() {
return userid;
}
public void setUserid(int userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserphone() {
return userphone;
}
public void setUserphone(String userphone) {
this.userphone = userphone;
}
@Override
public String toString() {
return "User{" +
"userid=" + userid +
", username='" + username + '\'' +
", userphone='" + userphone + '\'' +
'}';
}
}
//接口
public interface UserDao {
//定义增删改查
public List<User> getAll();
}
3、添加mapper文件
<?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">
<!--namespace="接口的完整路径"-->
<mapper namespace="com.java.dao.UserDao">
<!--id=方法名-->
<select id="getAll" resultType="com.java.bean.User">
SELECT * FROM USER1;
</select>
</mapper>
4、处理数据
public class Test1 {
public static void main(String[] args) {
try {
//1 加载配置文件
Reader reader = Resources.getResourceAsReader("mybatis.xml");
//2 得到sqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory build = builder.build(reader);
//3 得到sqlSession
SqlSession sqlSession = build.openSession();
//4 操作sql
List<User> users = sqlSession.selectList("com.java.dao.UserDao.getAll");
//5 遍历
for (User user:users){
System.out.println(user);
}
//6 关闭资源
sqlSession.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3、配置多数据源
1、<!--在mybatis.xml文件中配置多个数据源,通过不同的id指定-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--<property name="url" value="jdbc:mysql://localhost:3306/test"/>-->
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT&nullCatalogMeansCurrent = true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="development1">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--<property name="url" value="jdbc:mysql://localhost:3306/test"/>-->
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT&nullCatalogMeansCurrent = true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
2、在调用时加上对应的id
public class Test1 {
public static void main(String[] args) {
try {
//1 加载配置文件
Reader reader = Resources.getResourceAsReader("mybatis.xml");
//2 得到sqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory build = builder.build(reader,"development1");
//3 得到sqlSession
SqlSession sqlSession = build.openSession();
//4 操作sql
List<User> users = sqlSession.selectList("com.java.dao.UserDao.getAll");
//5 遍历
for (User user:users){
System.out.println(user);
}
//6 关闭资源
sqlSession.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、增删改查-----CRUD
//接口
public interface UserDao {
//根据id查询用户信息
public List<User> findByUserid(int userid);
//新增 需要新增的数据存储于单个对象
public int insertUser(User user);
//新增 需要新增的数据是单独的,必须存放于一个map集合中,然后在配置文件中读取它的key值
public int insertUser1(Map map);
//取最大值、最小值、平均值
public Map find();
}
//实现:
public static void main(String[] args) {
try {
//1 加载配置文件
Reader reader = Resources.getResourceAsReader("mybatis.xml");
//2 得到sqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory build = builder.build(reader,"development");
//3 得到sqlSession
SqlSession sqlSession = build.openSession();
//4 操作sql
//查询
User user = sqlSession.selectOne("com.java.dao.UserDao.findByUserid", 5);
//整个对象插入
User user = new User(1, "五哈", "876644455");
int insert = sqlSession.insert("com.java.dao.UserDao.insertUser", user);
sqlSession.commit();
System.out.println(insert);
//多值分开插入
Map map = new HashMap();
map.put("uname","王五");
map.put("uphone","123456282");
int insert = sqlSession.insert("com.java.dao.UserDao.insertUser1", map);
sqlSession.commit();
System.out.println(insert);
//查询 最大值、最小值
Map map= sqlSession.selectOne("com.java.dao.UserDao.find");
Set<Map.Entry> set = map.entrySet();
//5 遍历
for (Map.Entry o:set) {
System.out.println(set);
}
//5 遍历
System.out.println("user:"+user);
/*for (User user:users){
System.out.println(user);
}*/
//6 关闭资源
sqlSession.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
配置:
<?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">
<!--namespace="接口的完整路径"-->
<mapper namespace="com.java.dao.UserDao">
<!--id=方法名-->
<select id="getAll" resultType="com.java.bean.User">
SELECT * FROM USER1;
</select>
<select id="findByUserid" parameterType="int" resultType="com.java.bean.User" >
SELECT * FROM USER1 WHERE USERID = #{USERID};
</select>
<!--增删改返回的是收影响的行数,不需要配置resultType-->
<insert id="insertUser" parameterType="com.java.bean.User">
insert into user1 values(#{userid},#{username},#{userphone})
</insert>
<insert id="insertUser1" parameterType="com.java.bean.User">
insert into user1(username,userphone) values(#{uname},#{uphone})
</insert>
<select id="find" resultType="map">
select max(userid) 最大值,avg(userid) 平均值,min(userid) 最小值 from user1
/*select max(userid) ,avg(userid) ,min(userid) from user1*/
</select>
</mapper>
5、getMapper方法
6、ThreadLocal处理sqlSession
ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机 制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
public class TestThread {
private ThreadLocal<String> threadLocal=new ThreadLocal<String>();
private List<String> list=new ArrayList<String>();
class A extends Thread{
@Override
public void run() {
//存值
System.out.println("A线程开始存值");
threadLocal.set("thread内容");
list.add("list内容");
System.out.println("A---threadLocal="+threadLocal.get());
}
}
class B extends Thread{
@Override
public void run() {
// 取值
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B线程取数据");
System.out.println("b---threadLocal="+threadLocal.get());
System.out.println("list="+list.get(0));
}
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
TestThread.A a=testThread.new A();
TestThread.B b=testThread.new B();
a.start();
b.start();
}
}
优化:
public class SqlSessionUtil {
private static ThreadLocal<SqlSession>threadLocal=new ThreadLocal<SqlSession>();
private static SqlSessionFactory sqlSessionFactory;
static {
try {
Reader resourceAsReader = Resources.getResourceAsReader("mybatis.xml");
sqlSessionFactory= new SqlSessionFactoryBuilder().build(resourceAsReader,"a2");
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSession(){
SqlSession session = threadLocal.get();
if(session==null){
session = sqlSessionFactory.openSession();
threadLocal.set(session);
}
return session;
}
public static void closeSession(){
SqlSession session = threadLocal.get();
if(session!=null){
session.close();
threadLocal.remove();
}
}
}
起别名:
<configuration>
<!--给类起别名-->
<typeAliases>
<!--给类起别名-->
<!--<typeAlias type="com.java.bean.User" alias="user"></typeAlias>-->
<!--给包起别名-->
<package name="com.java.bean"/>
</typeAliases>
7、log4j显示sql语句
1、导入依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、添加log4j.properties属性文件
log4j.rootLogger=DEBUG, Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
8、mybatis复杂查询
1、In查询:
<!--配置文件-->
<?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">
<!--namespace="接口的完整路径"-->
<mapper namespace="com.java.dao.UserDao2">
<!--id=方法名-->
<!--List类型-->
<select id="finda" resultType="user">
select * from user1 where userid in
<foreach collection="list" item="uid" open="(" close=")" separator=",">
#{uid}
</foreach>
</select>
<!--数组类型-->
<select id="findb" resultType="user">
select * from user1 where userid in
<foreach collection="array" item="uid" open="(" close=")" separator=",">
#{uid}
</foreach>
</select>
<!--Map类型-->
<select id="findc" resultType="user">
select * from user1 where userid in
<foreach collection="id" item="uid" open="(" close=")" separator=",">
#{uid}
</foreach>
</select>
</mapper>
//实现类
public class Test6 {
public static void main(String[] args) {
SqlSession session = SqlSessionUtil.getSession();
UserDao2 mapper = session.getMapper(UserDao2.class);
/* List
List list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
List<User> user = mapper.finda(list);*/
/* 数组
int [] id = new int[]{1,2,3};
List<User> user = mapper.findb(id);*/
// map集合
List list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Map map = new HashMap();
map.put("id",list);
List<User> user = mapper.findc(map);
for (User u: user) {
System.out.println(u);
}
SqlSessionUtil.closeSession();
}
}
2、模糊查询和分页查询
模糊查询:
//接口类
public interface UserDao2 {
//模糊查询 根据姓名和编号进行查询
public List<User> findd(Map map);
public List<User> finde (User user);
}
<!--模糊查询 map集合-->
<select id="findd" resultType="user">
select * from user1 where 1=1
<if test="uid != null and uid != ''">
and userid=${uid}
</if>
<if test="uname != null and uname != ''" >
and username like '%${uname}%'
</if>
</select>
<!--模糊查询 User对象-->
<select id="finde" resultType="user">
select * from user1 where 1=1
<if test="userid != null and userid != ''">
and userid=${userid}
</if>
<if test="username != null and username != ''" >
and username like '%${username}%'
</if>
</select>
//实现类
public class Test7 {
public static void main(String[] args) {
SqlSession session = SqlSessionUtil.getSession();
UserDao2 mapper = session.getMapper(UserDao2.class);
/*Map map = new HashMap();
map.put("uid","1");
map.put("uname","五");
List<User> user = mapper.findd(map);*/
User user1 = new User();
user1.setUsername("五");
List<User> user = mapper.finde(user1);
for(User u: user){
System.out.println(u);
}
SqlSessionUtil.closeSession();
}
}
3、分页查询:
1、逻辑分页:
List<User> users = sqlSession.selectList("com.java.dao.UserDao.getAll",null,new RowBounds(0,3));
2、物理分页:
导包:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.6</version>
</dependency>
修改配置文件
<!--分页插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>
实现类:
public static void main(String[] args) {
try {
//1.加载配置文件
Reader reader = Resources.getResourceAsReader("mybatis.xml");
//2.得到sqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory build = builder.build(reader,"a2");
//3.得到SqlSession
SqlSession session = build.openSession();
//4.操作sql
//4.1 指定分页的参数
PageHelper.startPage(5,3);
//4.2 调取dao层方法
List<Student> list = session.selectList("com.yhp.dao.StudentDao.getall");//方法参数是被调取的sql的完整路径=namespace+id
//4.3 创建分页工具类对象
PageInfo<Student> info = new PageInfo<Student>(list);
//5.从分页数据中获得数据
for (Student student : info.getList()) {
System.out.println(student);
}
System.out.println("当前页条数:"+info.getSize());
System.out.println("总条数:"+info.getTotal());
System.out.println("总页数:"+info.getPages());
System.out.println("上一页:"+info.getPrePage()); //如果没有上一页,则返回0
System.out.println("下一页:"+info.getNextPage());
System.out.println("当前页:"+info.getPageNum());
System.out.println("显示条数:"+info.getPageSize());
//6.关闭资源
session.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
区间查询 :between 开始值 and 结束值
9、resultMap
1、、处理属性值和列名不匹配 单表关系
通过给列起别名,让别名=属性名,也可以实现数据对应
resultType=“指定返回值的类型”//当列名和属性名一致时使用
resultMap=“key 值” //1.当列名和属性名不一致 2.做多表查询时
mybatis 能实现的是单标的自动操作
<resultMap id="rs1" type="com.yhp.bean.Student">
<result property="age" column="stuage"></result>
</resultMap>
<!--id="方法名"-->
<select id="getall" resultMap="rs1">
select * from student
</select>
2、处理多表关系
一对多
<resultMap type="" id="自定义名称">
<id property="id" column="dept_id" /><!--主键列-->
<result property="java 属性名" column="列名" />
<collection property="属性名" ofType="java 类型">
<id property="属性名" column="列名" />
<result property="属性名" column="列名" />
</collection>
</resultMap>
多对一
<resultMap type="" id="">
<id property="" column="" />
<result property="" column="" />
<association property="" javaType="">
<id property="" column="" />
<result property="" column="" />
</association>
</resultMap>
一对一:
<!--配置文件-->
<?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">
<!--namespace="接口的完整路径"-->
<mapper namespace="com.yhp.dao.WifeDao">
<resultMap id="rs6" type="com.yhp.bean.Husband">
<id property="husId" column="husid"></id>
<result property="husName" column="husname"></result>
<association property="wife" javaType="com.yhp.bean.Wife">
<id property="wifeId" column="wifeid"></id>
<result property="wifeName" column="wifename"></result>
</association>
</resultMap>
<resultMap id="rs7" type="com.yhp.bean.Wife">
<id property="wifeId" column="wifeid"></id>
<result property="wifeName" column="wifename"></result>
<association property="husband" javaType="com.yhp.bean.Husband">
<id property="husId" column="husid"></id>
<result property="husName" column="husname"></result>
</association>
</resultMap>
<select id="findByhusId" resultMap="rs6">
select * from wife w,husband h
where w.wifeid=h.wid and h.husid=#{husid}
</select>
<select id="findByWifeId" resultMap="rs7">
select * from wife w,husband h
where w.wifeid=h.wid and w.wifeid=#{wifeid}
</select>
</mapper>
//接口
public interface WifeDao {
//根据丈夫查询妻子
public Husband findByhusId(int husid);
//根据妻子查询丈夫
public Wife findByWifeId(int wifeid);
}
//实现类
public class TestWife {
public static void main(String[] args) {
SqlSession session = SqlSessionUtil.getSession();
WifeDao mapper = session.getMapper(WifeDao.class);
Wife wife = mapper.findByWifeId(1);
System.out.println(wife.getWifeName()+","+wife.getHusband().getHusName());
Husband husband = mapper.findByhusId(1);
System.out.println(husband.getHusName()+","+husband.getWife().getWifeName());
SqlSessionUtil.closeSession();
}
}
10、缓存
**一级缓存:**SqlSession 的缓存 ------>自动开启
**二级缓存:**做到从不同的缓存中共享数据 SqlSessionFactory 的缓存 —>需要手动开启
配置:
<mapper namespace="接口路径">
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
</mapper>
eviction: 二级缓存中,缓存的对象从缓存中移除的策略,回收策略为先进先出
flushInterval: 刷新缓存的事件间隔,单位:毫秒
size: 缓存对象的个数
readOnly: 是否是只读的
11、Mybatis注解
<mapper class="com.dao.StudentDao"></mapper>
public interface StudentDao {
//增删改查
@Insert("insert into student(studentno,stuname,stuage) values(#{studentNo},#{stuName},#{age})")
@Options(useGeneratedKeys = true,keyProperty = "studentId")//获得新增数据的id
public int insertStu(Student student);
@Update("update student set studentno=#{studentNo},stuname=#{stuName} where studentid=#{studentId}")
public int updateStu(Student student);
@Select("select * from student")
@Results({
@Result(column = "stuage",property = "age")
})
public List<Student> findall();
@Select("select count(*) from student")
public int totalCount();
@Delete("delete from student where studentid=#{sid}")
public int deleteStu(int sid);
//计算id的最大值,最小值,平均值
@Select("select max(studentid) max,min(studentid) min,avg(studentid) avg from student")
public Map total2();
}
//测试
public class Demo1 {
public static void main(String[] args) {
SqlSession session = SqlSessionUtil.getSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
/* Student student = new Student();
student.setStudentNo("a1101");
student.setStuName("abc");
student.setAge(18);
int i = studentDao.insertStu(student);
session.commit();
System.out.println("i="+i+",id="+student.getStudentId());*/
/* Student student = new Student();
student.setStudentId(119);
student.setStudentNo("a1101");
student.setStuName("张娜");
student.setAge(20);
int i = studentDao.updateStu(student);
session.commit();
System.out.println("i="+i);*/
/*
List<Student> students = studentDao.findall();
for (Student student : students) {
System.out.println(student);
}*/
/* int i = studentDao.deleteStu(119);
session.commit();
System.out.println("i="+i);*/
/* int i = studentDao.totalCount();
System.out.println("total="+i);*/
Map map = studentDao.total2();
Set<Map.Entry> set = map.entrySet();
for (Map.Entry entry : set) {
System.out.println(entry);
}
SqlSessionUtil.closeSession();
}
}
12、lombok插件
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<scope>provided</scope>
</dependency>
lombok的使用 :
@Data 注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法
@Setter :注解在属性上;为属性提供 setting 方法 @Getter :注解在属性上;为属性提供 getting 方法
@Log4j :注解在类上;为类提供一个 属性名为log 的 log4j 日志对象
@NoArgsConstructor :注解在类上;为类提供一个无参的构造方法
@AllArgsConstructor :注解在类上;为类提供一个全参的构造方法
@Cleanup : 可以关闭流
@Builder : 被注解的类加个构造者模式
@Synchronized : 加个同步锁
@SneakyThrows : 等同于try/catch 捕获异常
@NonNull : 如果给参数加个这个注解 参数为null会抛出空指针异常
@Value : 注解和@Data类似,区别在于它会把所有成员变量默认定义为private final修饰,并且不会生成set方法。
@ToString 重写toString()方法
13、Mybatis自动化
作用:反向生成实体类,接口,mapper.xml
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
加载插件:
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<!--配置文件的路径-->
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
修改配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 配置生成器 -->
<generatorConfiguration>
<!--数据库驱动jar -->
<classPathEntry
location="F:\kaikeba\maven\maven_repository\mysql\mysql-connector-java\5.1.40\mysql-connector-java-5.1.40.jar" />
<context id="MyBatis" targetRuntime="MyBatis3">
<!--去除注释 -->
<commentGenerator>
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mytest"
userId="root"
password="123456">
</jdbcConnection>
<!--生成实体类 指定包名 以及生成的地址 (可以自定义地址,但是路径不存在不会自动创建
使用Maven生成在target目录下,会自动创建) -->
<javaModelGenerator targetPackage="com.yhp.bean"
targetProject="D:\mybatis3\src\main\java">
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--生成SQLmapper文件 -->
<sqlMapGenerator targetPackage="mapper"
targetProject="D:\mybatis3\src\main\resources">
</sqlMapGenerator>
<!--生成Dao文件,生成接口 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.yhp.dao"
targetProject="D:\mybatis3\src\main\java">
</javaClientGenerator>
<table tableName="student" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false"
enableSelectByExample="false" selectByExampleQueryId="false">
</table>
<table tableName="grade" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false"
enableSelectByExample="false" selectByExampleQueryId="false">
</table>
<table tableName="role" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false"
enableSelectByExample="false" selectByExampleQueryId="false">
</table> </context>
</generatorConfiguration>
运行:maven Project选项卡->plugins->找到mybatis-generator-core,双击运行就会自动生成
注意:运行一次即可,如果运行过程中,未完全成功。则将原来生成的代码删除后,再次运行。
切记!切记!切记