读书笔记-深入浅出spirngboot2
第一章
- springboot的依赖和自动配置
spring-boot-starter-web引入了下面的包
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.0.0.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.0.0.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.0.0.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.7.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>https://i.cnblogs.com/EditPosts.aspx?opt=1
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.4.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
第二章 自定义配置
- 在运行前还需要做一些配置
autoconfig中的配置类:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
//配置文件
@Configuration
//配置需要满足类型是Servlet
@ConditionalOnWebApplication(type = Type.SERVLET)
//需要是DispatcherServlet类
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
//自动配置servlet相关的属性,这样就能做到自动配置
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {
- 配置文件加载优先级:
bootstrap.properties优先于application.prperties
properties优先于yml
resources/config优先于resources/
第三章 全注解下的Spring IOC
- 所有IOC容器都要实现接口BeanFactory,他是一个顶级接口
//isSingleton判断是否单例,默认情况下bean都是单例(isSingleton返回true),如果是isPrototype返回true,则是多例模式,每次调用getBean会返回一个新的bean
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
- Spring在BeanFactory的基础上,设置了一个更高级的接口ApplicationContext,他是BeanFactory的子接口之一,实际上使用的都是ApplicationContext的实现类
- 简单注入bean的方法
1.声明实体类
public class User {
private Long id;
private String userName;
private String note;
}
2.增加配置类
@Configuration
public class AppConfig {
@Bean(name="user")
public User init(){
User user = new User();
user.setId(1L);
user.setUserName("aaa");
user.setNote("note_1");
return user;
}
}
3.使用
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User u = (User) ctx.getBean("user");
System.out.println(u.getId());
}
- 通过扫描注入bean
@ComponentScan会扫描当前包和子包
@Component("user")
public class User {
private Long id;
private String userName;
private String note;
}
@Configuration
@ComponentScan
public class AppConfig {
@Bean(name="user")
public User init(){
User user = new User();
user.setId(1L);
user.setUserName("aaa");
user.setNote("note_1");
return user;
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User u = (User) ctx.getBean(User.class);
System.out.println(u.getId());
}
- ComponentScan源码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//在一个类中可以重复定义
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
//定义扫描的包
@AliasFor("basePackages")
String[] value() default {};
//定义扫描的包
@AliasFor("value")
String[] basePackages() default {};
//定义扫描的类
Class<?>[] basePackageClasses() default {};
//Bean name 生成器
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
//作用域解析器
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
//作用域代理模式
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
//资源匹配模式
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
//启用默认过滤器
boolean useDefaultFilters() default true;
//当满足过滤器的条件时扫描
Filter[] includeFilters() default {};
//当不满足过滤器条件时扫描
Filter[] excludeFilters() default {};
//是否延迟初始化
boolean lazyInit() default false;
//定义过滤器
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
//过滤器类型
FilterType type() default FilterType.ANNOTATION;
//定义过滤的类
@AliasFor("classes")
Class<?>[] value() default {};
//定义过滤的类
@AliasFor("value")
Class<?>[] classes() default {};
//匹配方式
String[] pattern() default {};
}
}
- SpringBootApplication源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
//自定义排除的扫描类
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//通过类型排除自动配置类
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
//通过名称排除自动配置类
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
//定义扫描包
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
//定义被扫描的类
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
- 自定义第三方bean
Configuration
@ComponentScan(basePackages = "com.springboot.chapter3.*",
excludeFilters = {@Filter(classes = {})})
public class AppConfig {
@Bean(name = "dataSource")
public DataSource getDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/chapter3");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
- @AutoWired
默认是by type注入
如果一个接口有多个实现类,在注入时会有歧义,利用@Primary和@Quelifier可以消除歧义
例如:
@Primary: 当发现多个同样类型的bean时,可以优先注入
@Quelifier: 和@AutoWired一起使用时,可以通过类型和Quelifier定义的名称一起找到bean - bean生命周期
ComponentScan还有一个配置项lazyInit,只可以配置boolean值,默认是false,也就是默认不进行延迟初始化
配置为true的话就是等到使用的时候才进行注入
bean生命周期
- 前和后预初始化方法(针对所有的bean都生效)
@Component
public class BeanPostProcessorExample implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor调用postProcessBeforeInitialization方法,参数【"
+ bean.getClass().getSimpleName()+ "】【" +beanName+"】 ");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor调用postProcessAfterInitialization方法,参数【"
+ bean.getClass().getSimpleName()+ "】【" +beanName+"】 ");
return bean;
}
}
- 使用属性文件
属性文件依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
有了依赖,可以直接使用application.properties
如果配置文件中是database.driverName=**这种,可以直接在配置类中使用@ConfigurationProperties( "database"),否则可以使用@Value单个属性配置
如果不是application.properties可以使用自定义加载 @PropertySource(value={"classpath:jdbc.properties"}, ignoreResourceNotFound=true)
- 条件装配Bean
有时候有一些客观原因使bean无法初始化,如:在数据库连接池中漏掉一些配置使数据库连接不上,这个时候可以使用@Condition,当条件不满足的时候不加载
加上@Conditional(DatabaseConditional.class)后,需要增加实现类,在实现类中添加过滤的规则,如果过滤的规则如果不符合则不装配这个bean
@Bean(name = "dataSource", destroyMethod = "close")
@Conditional(DatabaseConditional.class)
public DataSource getDataSource(
@Value("${database.driverName}") String driver,
@Value("${database.url}") String url,
@Value("${database.username}") String username,
@Value("${database.password}") String password
) {
Properties props = new Properties();
props.setProperty("driver", driver);
props.setProperty("url", url);
props.setProperty("username", username);
props.setProperty("password", password);
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
public class DatabaseConditional implements Condition {
@Override
/*
*
* @param context 条件上下文
* @param
*/
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("database.driverName") && env.containsProperty("database.url")
&& env.containsProperty("database.username") && env.containsProperty("database.password");
}
}
Bean作用域
- 常用bean作用域有两种: 单例和原型
- @Profile 区分不同的环境
@Bean(name = "dataSource", destroyMethod = "close")
@Profile("dev")
public DataSource getDevDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/dev_spring_boot");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
@Bean(name = "dataSource", destroyMethod = "close")
@Profile("test")
public DataSource getTestDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/test_spring_boot");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
spring启动时先判断有没有spring.profiles.active,然后判断spring.profiles.default,如果这两个参数都没有配置,spring就不会启动Profile机制,被@Profile标志的bean都不会被加载
- ImportResource 引入对应的XML文件:在配置类加上@ImportResource(value = {"classpath:spring-other.xml"})
第五章 访问数据库
- mybatis配置结构图
第六章 事务处理
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
//通过bean name指定事务管理器
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
//指定传播行为
Propagation propagation() default Propagation.REQUIRED;
//指定隔离级别
Isolation isolation() default Isolation.DEFAULT;
//指定超时时间
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
//只读事物
boolean readOnly() default false;
//方法发生指定异常时回滚,默认是所有都回滚
Class<? extends Throwable>[] rollbackFor() default {};
//方法发生指定异常名称时回滚,默认所有异常回滚
String[] rollbackForClassName() default {};
//方法指定异常不回滚
Class<? extends Throwable>[] noRollbackFor() default {};
//方法指定异常名称不回滚
String[] noRollbackForClassName() default {};
}
@Transactional 可以放在接口也可以放在实现类上,推荐放在实现类上,使用接口的话只能用jdk动态代理,不能使用CGLIB动态代理
- 事务管理器
- 事务隔离级别
1.未提交读: 允许一个事务读取另外一个事务没有提交的数据(比较危险,实际很少使用,但是并发能力高),会出现脏读
2.读写提交: 一个事务只能读取另外一个事务已经提交的数据,不能读取未提交的数据
会出现不可重复读:
3.可重复读:
会出现幻读
4.串行化:sql完全串行执行
- 传播行为
在spring中,当一个方法调用另外一个方法时,可以让事务采取不同的策略工作
public enum Propagation {
//如果当前存在事务则沿用当前事务,否则新建一个事务运行字方法
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
//如果当前存在事务则沿用当前事务,否则以无事务方式运行
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
//必须使用事务,如果当前没有事务则抛异常,如果当前存在事务则沿用当前事务
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
//无论当前事务是否存在,都新建运行事务的方法,与当前事务独立
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
//不支持事务,如果当前有事务,则挂起事务运行方法
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
//不支持事务,如果当前有事务则抛异常,如果没有则继续执行
NEVER(TransactionDefinition.PROPAGATION_NEVER),
//在当前方法调用字方法时,如果子方法发生异常,只回滚子方法的sql,不回滚当前事务
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) { this.value = value; }
public int value() { return this.value; }
}
- @Transactional自调用失效
spring事务的实现方式是AOP,AOP原理是动态代理,在自调用的过程中,是类自身的调用,而不是代理对象去调用,就不会产生AOP,
这样Spring就不能把你的代码织入约定的流程中,为了使事务有效,要改成用一个service去调用另外一个service,这样才会触发AOP - 事务不生效原因汇总:
1.没有被spring管理
2.方法不是public,只有public的方法才会被代理
3.自调用
4.数据源没有配置事务管理器
5.数据库引擎不支持事务
6.异常被吃了,没有抛出来
7.异常配型配置错误,默认的是RuntimeException如果想抛其他类型,加上@Transactional(rollbackFor = Exception.class)
第七章 使用redis
- Spring通过RedisConnection操作redis,RedisConnection是对原生的redis进行封装,要获取RedisConnection对象,需要通过RedisConnectionFactory生成
所有使用redis的第一步是配置这个工厂,主要是配置Redis连接池
redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!--不依赖Redis的异步客户端lettuce -->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入Redis的客户端驱动jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
配置Redis连接池和配置RedisTemplate
public class RedisConfig {
private RedisConnectionFactory connectionFactory = null;
@Bean(name = "redisConnectionFactory")
public RedisConnectionFactory initConnectionFactory() {
if (this.connectionFactory != null) {
return this.connectionFactory;
}
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大空闲数
poolConfig.setMaxIdle(50);
// 最大连接数
poolConfig.setMaxTotal(100);
// 最大等待毫秒数
poolConfig.setMaxWaitMillis(2000);
// 创建Jedis连接工厂
JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);
// 配置Redis连接服务器
RedisStandaloneConfiguration rsc = connectionFactory.getStandaloneConfiguration();
rsc.setHostName("192.168.20.4");
rsc.setPort(6379);
//rsc.setPassword(RedisPassword.of("123456"));
this.connectionFactory = connectionFactory;
return connectionFactory;
}
@Bean(name="redisTemplate")
public RedisTemplate<Object, Object> initRedisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(initConnectionFactory());
RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(stringRedisSerializer);
return redisTemplate;
}
}
测试RedisTemplate
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(RedisConfig.class);
RedisTemplate redisTemplate = ctx.getBean(RedisTemplate.class);
redisTemplate.opsForValue().set("key1", "value1");
redisTemplate.opsForHash().put("hash", "field", "hvalue");
ctx.close();
}
-
如果没有配置序列化器,则默认使用JdkSerializationRedisSerializer序列化器
-
使用字符串序列化器
-
StringRedisTemplate,继承RedisTemplate,只是提供了字符串的操作
-
Redis能支持7中数据类型
字符串,散列,列表,集合,有序集合,基数和地理位置
-
如果需要连续操作多个命令,则可以使用以下接口
-
springboot中使用redis
1.配置属性
2.修改RedisTemplate序列化器,不然就会使用默认的JdkSerializationRedisSerializer序列化器
3.使用RedisTemplate操作数据 -
Redis事务
-
流水线执行模式提高速度
public Map<String, Object> testPipeline() {
Long start = System.currentTimeMillis();
List list = (List) redisTemplate.executePipelined((RedisOperations operations) -> {
for (int i = 1; i <= 100000; i++) {
operations.opsForValue().set("pipeline_" + i, "value_" + i);
String value = (String) operations.opsForValue().get("pipeline_" + i);
if (i == 100000) {
System.out.println("命令只是进入队列,所以值为空【" + value + "】");
}
}
return null;
});
Long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "毫秒。");
Map<String, Object> map = new HashMap<String, Object>();
map.put("success", true);
return map;
}
- Redis发布订阅
消息监听器
@Component
public class RedisMessageListener implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
// 消息体
String body = new String(message.getBody());
// 渠道名称
String topic = new String(pattern);
System.out.println(body);
System.out.println(topic);
}
}
// 设置RedisTemplate的序列化器
private void initRedisTemplate() {
RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
}
// RedisTemplate
@Autowired
private RedisTemplate redisTemplate = null;
// Redis连接工厂
@Autowired
private RedisConnectionFactory connectionFactory = null;
// Redis消息监听器
@Autowired
private MessageListener redisMsgListener = null;
// 任务池
private ThreadPoolTaskScheduler taskScheduler = null;
/**
* 创建任务池,运行线程等待处理Redis的消息
*
* @return
*/
@Bean
public ThreadPoolTaskScheduler initTaskScheduler() {
if (taskScheduler != null) {
return taskScheduler;
}
taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(20);
return taskScheduler;
}
/**
* 定义Redis的监听容器
*
* @return 监听容器
*/
@Bean
public RedisMessageListenerContainer initRedisContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
// Redis连接工厂
container.setConnectionFactory(connectionFactory);
// 设置运行任务池
container.setTaskExecutor(initTaskScheduler());
// 定义监听渠道,名称为topic1
Topic topic = new ChannelTopic("topic1");
// 使用监听器监听Redis的消息
container.addMessageListener(redisMsgListener, topic);
return container;
}
public static void main(String[] args) {
SpringApplication.run(Chapter7Application.class, args);
}
当在redis执行publish topic1 msg时会收到信息
- 使用lua脚本
public Map<String, Object> testLua2(String key1, String key2, String value1, String value2) {
// 定义Lua脚本
String lua = " redis.call('set', KEYS[1], ARGV[1]) \n"
+ " redis.call('set', KEYS[2], ARGV[2]) \n"
+ " local str1 = redis.call('get', KEYS[1]) \n"
+ " local str2 = redis.call('get', KEYS[2]) \n"
+ " if str1 == str2 then \n" + "return 1 \n"
+ " end \n"
+ " return 0 \n";
System.out.println(lua);
// 结果返回为Long
DefaultRedisScript<Long> rs = new DefaultRedisScript<Long>();
rs.setScriptText(lua);
rs.setResultType(Long.class);
// 采用字符串序列化器
RedisSerializer<String> stringSerializer = redisTemplate.getStringSerializer();
// 定义key参数
List<String> keyList = new ArrayList<>();
keyList.add(key1);
keyList.add(key2);
// 传递两个参数值,其中第一个序列化器是key的序列化器,第二个序列化器是参数的序列化器
Long result = (Long) redisTemplate.execute(rs, stringSerializer, stringSerializer, keyList, value1, value2);
Map<String, Object> map = new HashMap<String, Object>();
map.put("result", result);
return map;
}
第八章 使用MongoDb
- 实体类加上@Document 标识为MongoDB文档
- @Field("user_name") MongoDB一般用下划线表示字段,所以要加别名
操作MongoDB的方法
@Service
public class UserServiceImpl implements UserService {
// 注入MongoTemplate对象
@Autowired
private MongoTemplate mongoTmpl = null;
@Override
public User getUser(Long id) {
return mongoTmpl.findById(id, User.class);
// 如果只需要获取第一个也可以采用如下查询方法
// Criteria criteriaId = Criteria.where("id").is(id);
// Query queryId = Query.query(criteriaId);
// return mongoTmpl.findOne(queryId, User.class);
}
@Override
public List<User> findUser(String userName, String note, int skip, int limit) {
// 将用户名称和备注设置为模糊查询准则
Criteria criteria = Criteria.where("user_name").regex(userName).and("note").regex(note);
// 构建查询条件,并设置分页跳过前skip个,至多返回limit个
Query query = Query.query(criteria).limit(limit).skip(skip);
// 执行
List<User> userList = mongoTmpl.find(query, User.class);
return userList;
}
@Override
public void saveUser(User user) {
// 使用名称为user文档保存用户信息
mongoTmpl.save(user, "user");
// 如果文档采用类名首字符小写,则可以这样保存
// mongoTmpl.save(user);
}
@Override
public DeleteResult deleteUser(Long id) {
// 构建id相等的条件
Criteria criteriaId = Criteria.where("id").is(id);
// 查询对象
Query queryId = Query.query(criteriaId);
// 删除用户
DeleteResult result = mongoTmpl.remove(queryId, User.class);
return result;
}
@Override
public UpdateResult updateUser(Long id, String userName, String note) {
// 确定要更新的对象
Criteria criteriaId = Criteria.where("id").is(id);
Query query = Query.query(criteriaId);
// 定义更新对象,后续可变化的字符串代表排除在外的属性
Update update = Update.update("user_name", userName);
update.set("note", note);
// 更新单个对象
UpdateResult result = mongoTmpl.updateFirst(query, update, User.class);
// 更新多个对象
// UpdateResult result2 = mongoTmpl.updateMulti(query, update, User.class);
return result;
}
}