diff --git a/spring-boot-mongodb/spring-boot-mongodb/pom.xml b/spring-boot-mongodb/spring-boot-mongodb/pom.xml index ed9d7c5c0..6da153f0f 100644 --- a/spring-boot-mongodb/spring-boot-mongodb/pom.xml +++ b/spring-boot-mongodb/spring-boot-mongodb/pom.xml @@ -43,7 +43,17 @@ - + + org.projectlombok + lombok + provided + + + org.projectlombok + lombok + provided + + diff --git a/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/MongoDBApplication.java b/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/MongoDBApplication.java index b8c63d563..262d45720 100644 --- a/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/MongoDBApplication.java +++ b/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/MongoDBApplication.java @@ -1,12 +1,29 @@ package com.neo; +import com.neo.model.User; +import com.neo.repository.impl.UserRepositoryImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -@SpringBootApplication -public class MongoDBApplication { +import java.util.ArrayList; +import java.util.List; +@SpringBootApplication +public class MongoDBApplication implements ApplicationRunner { + @Autowired + public UserRepositoryImpl userRepository; public static void main(String[] args) { SpringApplication.run(MongoDBApplication.class, args); } + + @Override + public void run(ApplicationArguments args) throws Exception { +// User user = new User(2L,"neo","123456"); +// userRepository.saveUser(user); + User userFind = userRepository.findUserByUserName("neo"); + System.out.print(userFind); + } } diff --git a/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/model/User.java b/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/model/User.java index c15340ae7..08c250764 100644 --- a/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/model/User.java +++ b/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/model/User.java @@ -1,10 +1,19 @@ package com.neo.model; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + import java.io.Serializable; /** * Created by summer on 2017/5/5. */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder public class User implements Serializable { private static final long serialVersionUID = -3258839839160856613L; private Long id; diff --git a/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/repository/impl/UserRepositoryImpl.java b/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/repository/impl/UserRepositoryImpl.java index 8c96f5a42..ddbdee03b 100644 --- a/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/repository/impl/UserRepositoryImpl.java +++ b/spring-boot-mongodb/spring-boot-mongodb/src/main/java/com/neo/repository/impl/UserRepositoryImpl.java @@ -10,6 +10,9 @@ import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; + /** * Created by summer on 2017/5/5. */ @@ -36,8 +39,8 @@ public void saveUser(User user) { @Override public User findUserByUserName(String userName) { Query query=new Query(Criteria.where("userName").is(userName)); - User user = mongoTemplate.findOne(query , User.class); - return user; + List user = mongoTemplate.find(query , User.class); + return user.get(0); } /** diff --git a/spring-boot-mongodb/spring-boot-mongodb/src/main/resources/application.properties b/spring-boot-mongodb/spring-boot-mongodb/src/main/resources/application.properties index d2c101751..11411407c 100644 --- a/spring-boot-mongodb/spring-boot-mongodb/src/main/resources/application.properties +++ b/spring-boot-mongodb/spring-boot-mongodb/src/main/resources/application.properties @@ -1,5 +1,4 @@ spring.application.name=spring-boot-mongodb - -spring.data.mongodb.uri=mongodb://119.0.0.6:27017/test +spring.data.mongodb.uri=mongodb://localhost:27017/test diff --git a/spring-boot-mybatis/spring-boot-mybatis-annotation/src/main/java/com/neo/web/UserController.java b/spring-boot-mybatis/spring-boot-mybatis-annotation/src/main/java/com/neo/web/UserController.java index 25144b362..658b59f99 100644 --- a/spring-boot-mybatis/spring-boot-mybatis-annotation/src/main/java/com/neo/web/UserController.java +++ b/spring-boot-mybatis/spring-boot-mybatis-annotation/src/main/java/com/neo/web/UserController.java @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; - +import org.springframework.web.bind.annotation.RequestMethod; import com.neo.model.User; import com.neo.mapper.UserMapper; @@ -27,21 +27,19 @@ public User getUser(Long id) { User user=userMapper.getOne(id); return user; } - - @RequestMapping("/add") + + @RequestMapping(value = "/add", method = RequestMethod.POST) public void save(User user) { - userMapper.insert(user); + userMapper.insert(user); } - + @RequestMapping(value="update") public void update(User user) { userMapper.update(user); } - + @RequestMapping(value="/delete/{id}") public void delete(@PathVariable("id") Long id) { userMapper.delete(id); } - - } \ No newline at end of file diff --git a/spring-boot-mybatis/spring-boot-mybatis-annotation/src/main/resources/application.properties b/spring-boot-mybatis/spring-boot-mybatis-annotation/src/main/resources/application.properties index 06400e91a..83a421b7b 100644 --- a/spring-boot-mybatis/spring-boot-mybatis-annotation/src/main/resources/application.properties +++ b/spring-boot-mybatis/spring-boot-mybatis-annotation/src/main/resources/application.properties @@ -1,6 +1,6 @@ mybatis.type-aliases-package=com.neo.model - +server.port=9090 spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true spring.datasource.username=root -spring.datasource.password=root +spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver \ No newline at end of file diff --git a/spring-boot-redis/pom.xml b/spring-boot-redis/pom.xml index a358286ae..f1ac6ca45 100644 --- a/spring-boot-redis/pom.xml +++ b/spring-boot-redis/pom.xml @@ -36,6 +36,11 @@ org.springframework.boot spring-boot-starter-web + + com.alibaba + fastjson + 2.0.31 + org.springframework.session spring-session-data-redis diff --git a/spring-boot-redis/src/main/java/com/neo/RedisApplication.java b/spring-boot-redis/src/main/java/com/neo/RedisApplication.java index 9c41bedc0..30c4ace22 100644 --- a/spring-boot-redis/src/main/java/com/neo/RedisApplication.java +++ b/spring-boot-redis/src/main/java/com/neo/RedisApplication.java @@ -5,7 +5,6 @@ @SpringBootApplication public class RedisApplication { - public static void main(String[] args) { SpringApplication.run(RedisApplication.class, args); } diff --git a/spring-boot-redis/src/main/java/com/neo/config/RedisConfig.java b/spring-boot-redis/src/main/java/com/neo/config/RedisConfig.java index f26748985..d229e3bcc 100644 --- a/spring-boot-redis/src/main/java/com/neo/config/RedisConfig.java +++ b/spring-boot-redis/src/main/java/com/neo/config/RedisConfig.java @@ -1,34 +1,43 @@ package com.neo.config; -import java.lang.reflect.Method; - -import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.CachingConfigurerSupport; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.cache.interceptor.KeyGenerator; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; - +/** + * Redis配置 + */ @Configuration -@EnableCaching -public class RedisConfig extends CachingConfigurerSupport{ - - @Bean - public KeyGenerator keyGenerator() { - return new KeyGenerator() { - @Override - public Object generate(Object target, Method method, Object... params) { - StringBuilder sb = new StringBuilder(); - sb.append(target.getClass().getName()); - sb.append(method.getName()); - for (Object obj : params) { - sb.append(obj.toString()); - } - return sb.toString(); - } - }; +public class RedisConfig { + + /** + * + * @param redisTemplate the redis template + * @return the redis repository + */ + @Bean + public RedisTemplate redisTemplate(RedisTemplate redisTemplate) { + // 使用Jackson2JsonRedisSerialize 替换默认序列化 + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(objectMapper); + StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); + // key采用String的序列化方式 + redisTemplate.setKeySerializer(stringRedisSerializer); + // hash的key也采用String的序列化方式 + redisTemplate.setHashKeySerializer(stringRedisSerializer); + // value序列化方式采用jackson + redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); + // hash的value序列化方式采用jackson + redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); + redisTemplate.afterPropertiesSet(); + return redisTemplate; } } \ No newline at end of file diff --git a/spring-boot-redis/src/main/java/com/neo/config/SessionConfig.java b/spring-boot-redis/src/main/java/com/neo/config/SessionConfig.java deleted file mode 100644 index 400f03fd7..000000000 --- a/spring-boot-redis/src/main/java/com/neo/config/SessionConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.neo.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; - -@Configuration -@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30) -public class SessionConfig { -} \ No newline at end of file diff --git a/spring-boot-redis/src/main/java/com/neo/service/RedisService.java b/spring-boot-redis/src/main/java/com/neo/service/RedisService.java new file mode 100644 index 000000000..0ba19bd32 --- /dev/null +++ b/spring-boot-redis/src/main/java/com/neo/service/RedisService.java @@ -0,0 +1,373 @@ +package com.neo.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.support.atomic.RedisAtomicLong; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * spring redis 工具类 + **/ +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +@Component +public class RedisService { + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 判断key是否存在 + * + * @param key 缓存的键值 + * @return boolean + **/ + public boolean exists(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public long deleteObject(final Collection collection) { + return redisTemplate.delete(collection); + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList, long ttlInSeconds) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + redisTemplate.expire(key, ttlInSeconds, TimeUnit.SECONDS); + return count == null ? 0 : count; + } + + /** + * 入队列 + * + * @param key 缓存的键值 + * @param data 待缓存的List数据 + * @return 缓存的对象 + */ + public long rightPush(final String key, final T data, long ttlInSeconds) { + Long count = redisTemplate.opsForList().rightPush(key, data); + redisTemplate.expire(key, ttlInSeconds, TimeUnit.SECONDS); + return count == null ? 0 : count; + } + + /** + * 出队列 + * + * @param key 缓存的键值 + * @return 缓存的对象 + */ + public T leftPop(final String key, Class clazz) { + T result = (T) redisTemplate.opsForList().leftPop(key); + return result; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long replaceCacheList(final String key, final List dataList) { + redisTemplate.delete(key); + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 缓存data数据 + * + * @param key 缓存的键值 + * @param data 待缓存的数据 + * @return 缓存的对象 + */ + public long putCacheList(final String key, final T data) { + Long count = redisTemplate.opsForList().rightPush(key, data); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public T getCacheFromList(final String key, int index) { + List list = redisTemplate.opsForList().range(key, index, index); + if (CollectionUtils.isEmpty(list)) { + return null; + } + return list.get(0); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 缓存Data + * + * @param key 缓存键值 + * @param data 缓存的数据 + * @return 缓存数据的对象 + */ + public Long putCacheSet(final String key, final T data) { + return redisTemplate.opsForSet().add(key, data); + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value, long ttlInSeconds) { + redisTemplate.opsForHash().put(key, hKey, value); + redisTemplate.expire(key, ttlInSeconds, TimeUnit.SECONDS); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } + + /** + * 自增 + * + * @param key + * @return + */ + public Long generate(String key) { + RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); + return entityIdCounter.getAndIncrement(); + } + + /** + * 自增 + * + * @param key + * @return + */ + public Long getLong(String key) { + RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); + return entityIdCounter.get(); + } + + public void convertAndSend(String channel, Object message) { + redisTemplate.convertAndSend(channel, message); + } + + /** + * 频率限制 移动窗口 + * + * @param key + * @param period + * @param maxCount + * @return + */ + public boolean rateLimit(String key, int period, int maxCount) { + long nowTs = System.currentTimeMillis(); + List objects = redisTemplate.executePipelined((RedisCallback) connection -> { + connection.openPipeline(); + // connection.zAdd(key.getBytes(), (double) nowTs, ("" + nowTs).getBytes()); 如果写在这表示当前请求也需要统计,表示限定时间内不能再次触发 + connection.zRemRangeByScore(key.getBytes(), 0, nowTs - period * 1000); + connection.zCard(key.getBytes()); + connection.expire(key.getBytes(), period + 1); // 预防冷数据过期场景 + return null; + }); + if (CollectionUtils.isEmpty(objects)) { + return true; + } + boolean limit = (Long) objects.get(1) < maxCount; + if (limit) { + redisTemplate.opsForZSet().add(key, "" + nowTs, nowTs); + } + return limit; + } + +} + diff --git a/spring-boot-redis/src/main/java/com/neo/web/UserController.java b/spring-boot-redis/src/main/java/com/neo/web/UserController.java index 99166a0df..856fe6967 100644 --- a/spring-boot-redis/src/main/java/com/neo/web/UserController.java +++ b/spring-boot-redis/src/main/java/com/neo/web/UserController.java @@ -1,33 +1,28 @@ package com.neo.web; +import com.neo.service.RedisService; import jakarta.servlet.http.HttpSession; -import org.springframework.cache.annotation.Cacheable; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; - import com.neo.model.User; - import java.util.UUID; +import java.util.concurrent.TimeUnit; @RestController public class UserController { - + @Autowired + private RedisService redisService; @RequestMapping("/getUser") - @Cacheable(value="user-key") public User getUser() { User user=new User("aa@126.com", "aa", "aa123456", "aa","123"); + this.redisService.setCacheObject("aa@126.com", user, 1000L, TimeUnit.SECONDS); System.out.println("若下面没出现“无缓存的时候调用”字样且能打印出数据表示测试成功"); return user; } - - @RequestMapping("/uid") - String uid(HttpSession session) { - UUID uid = (UUID) session.getAttribute("uid"); - if (uid == null) { - uid = UUID.randomUUID(); - } - session.setAttribute("uid", uid); - return session.getId(); + public String uid() { + User user = this.redisService.getCacheObject("aa@126.com"); + return user.toString(); } } \ No newline at end of file diff --git a/spring-boot-redis/src/main/resources/application.properties b/spring-boot-redis/src/main/resources/application.properties index 2d4d7e89b..abc9d5d77 100644 --- a/spring-boot-redis/src/main/resources/application.properties +++ b/spring-boot-redis/src/main/resources/application.properties @@ -1,17 +1,7 @@ -# REDIS -# Redis\u6570\u636E\u5E93\u7D22\u5F15\uFF08\u9ED8\u8BA4\u4E3A0\uFF09 -spring.redis.database=0 -# Redis\u670D\u52A1\u5668\u5730\u5740 spring.redis.host=localhost -# Redis\u670D\u52A1\u5668\u8FDE\u63A5\u7AEF\u53E3 spring.redis.port=6379 -# Redis\u670D\u52A1\u5668\u8FDE\u63A5\u5BC6\u7801\uFF08\u9ED8\u8BA4\u4E3A\u7A7A\uFF09 -spring.redis.password= -# \u8FDE\u63A5\u6C60\u6700\u5927\u8FDE\u63A5\u6570\uFF08\u4F7F\u7528\u8D1F\u503C\u8868\u793A\u6CA1\u6709\u9650\u5236\uFF09 \u9ED8\u8BA4 8 +#spring.redis.password= spring.redis.lettuce.pool.max-active=8 -# \u8FDE\u63A5\u6C60\u6700\u5927\u963B\u585E\u7B49\u5F85\u65F6\u95F4\uFF08\u4F7F\u7528\u8D1F\u503C\u8868\u793A\u6CA1\u6709\u9650\u5236\uFF09 \u9ED8\u8BA4 -1 spring.redis.lettuce.pool.max-wait=-1 -# \u8FDE\u63A5\u6C60\u4E2D\u7684\u6700\u5927\u7A7A\u95F2\u8FDE\u63A5 \u9ED8\u8BA4 8 spring.redis.lettuce.pool.max-idle=8 -# \u8FDE\u63A5\u6C60\u4E2D\u7684\u6700\u5C0F\u7A7A\u95F2\u8FDE\u63A5 \u9ED8\u8BA4 0 spring.redis.lettuce.pool.min-idle=0 diff --git a/spring-boot-web/src/main/java/com/neo/web/ThymeleafController.java b/spring-boot-web/src/main/java/com/neo/web/ThymeleafController.java index c384bd1d8..a0f2aa0a8 100644 --- a/spring-boot-web/src/main/java/com/neo/web/ThymeleafController.java +++ b/spring-boot-web/src/main/java/com/neo/web/ThymeleafController.java @@ -14,12 +14,10 @@ public class ThymeleafController { @RequestMapping("/hi") public String hello(Locale locale, Model model) { model.addAttribute("greeting", "Hello!"); - Date date = new Date(); DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); String formattedDate = dateFormat.format(date); model.addAttribute("currentTime", formattedDate); - return "hello"; }