=============== LIBRARY RULES =============== From library maintainers: - Always use @XEntity to map entity classes to database tables - Repository interfaces must extend IXRepository and be annotated with @XRepository - Application class requires @XRepositoryScan, @MapperScan, and @ComponentScan with framework packages - Use save() for upsert (INSERT ON DUPLICATE KEY UPDATE), insert() for INSERT only - Use modify() for selective update (non-null fields only), update() for full column update - Query derivation supports findBy, findAllBy, countBy, existsBy, deleteBy prefixes with And combination - Use XPagination with XOrder for paginated queries, returns XPage - MANDATORY: Every member variable (Entity, DTO, Request, Response, VO) and every enum item MUST have a detailed Javadoc comment including purpose, example values, format rules, constraints, and allowed values. - `@XPaginationDefault` defaults: `page=1`, `size=10`, `direction=DESC`. Sort without direction defaults to ASC. - `XSessionResolver` auto-detects `SessionData` subclass parameters — no annotation required on the controller parameter. - `@XColumn` is only needed for: primary keys, custom column names, or insert/update control. Regular fields auto-map via camelCase → snake_case — do NOT add `@XColumn` to every field. - `@XDefaultValue(value="X")` alone does NOT work — `isDBDefaultUsed` defaults to `true`, so the value is ignored. Must set `isDBDefaultUsed=false` for literal values. - `@XRestServiceScan` is required on the application class when using `@XRestService` declarative REST clients.`XWebClient` beans can be registered via `axim.web-client.services.{name}={url}` in properties, then injected with `@Qualifier`. - Session token format is NOT JWT — it uses custom `Base64(payload).HmacSHA256(signature)`. Do not use JWT libraries. - JSON date format is `yyyy-MM-dd HH:mm:ss`, not ISO 8601. ### Token Format Example Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Provides an example of the custom token format used by the framework, which is Base64 encoded payload followed by an HMAC-SHA256 signature. ```text {BASE64_ENCODED_PAYLOAD}.{HMAC_SHA256_SIGNATURE} Example: eyJ1c2VySWQiOjEsInNlc3Npb25JZCI6ImFiYzEyMyJ9.a2b3c4d5e6f7... NOT JWT format (header.payload.signature) ``` -------------------------------- ### Controller with Default Pagination Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Example of a controller endpoint using default pagination settings, sorting by creation date in descending order. ```java @GetMapping public XPage listUsers( @XPaginationDefault(size = 20, column = "createdAt", direction = XDirection.DESC) XPagination pagination) { return userRepository.findAll(pagination); } ``` -------------------------------- ### Secure Session Configuration Example Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Illustrates the secure configuration of the session secret key, emphasizing the need for a strong, random key in production. ```properties # ✓ SECURE — HMAC-SHA256 signature verification enabled axim.rest.session.secret-key=a-strong-random-secret-key-at-least-32-chars ``` -------------------------------- ### Axim REST Framework Application Setup Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Configure your main application class with the necessary Spring Boot and Axim framework annotations. Include `@XRestServiceScan` if you are using declarative REST clients. ```java @ComponentScan({"one.axim.framework.rest", "one.axim.framework.mybatis", "com.myapp"}) @SpringBootApplication @XRepositoryScan("com.myapp.repository") @MapperScan({"one.axim.framework.mybatis.mapper", "com.myapp.mapper"}) @XRestServiceScan("com.myapp.client") // Only if using @XRestService REST clients public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } ``` -------------------------------- ### Entity Inheritance Example Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates entity inheritance where parent class fields are automatically included in the mapping. The example shows a Partner entity extending BaseEntity. ```java public class BaseEntity { @XColumn(isPrimaryKey = true, isAutoIncrement = true) private Long id; @XDefaultValue(value = "NOW()", isDBValue = true) private LocalDateTime createdAt; @XDefaultValue(updateValue = "NOW()", isDBValue = true) private LocalDateTime updatedAt; } @Data @XEntity("partners") public class Partner extends BaseEntity { private String name; private String status; } // All fields mapped: id, createdAt, updatedAt, name, status ``` -------------------------------- ### Throwing Exceptions with ErrorCode Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates how to throw built-in exceptions using predefined ErrorCode constants, with examples showing how to include additional descriptive messages. ```java @Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; public User getUser(Long id) { User user = userRepository.findOne(id); if (user == null) { // Use built-in ErrorCode constant throw new NotFoundException(NotFoundException.NOT_FOUND_API); } return user; } public void validateToken(String token) { if (token == null) { throw new UnAuthorizedException(UnAuthorizedException.NOT_FOUND_ACCESS_TOKEN); } if (isExpired(token)) { // With additional description throw new UnAuthorizedException( UnAuthorizedException.EXPIRE_ACCESS_TOKEN, "Token expired at 2024-01-01" ); } } public void validateRequest(UserCreateRequest req) { if (req.getEmail() == null) { throw new InvalidRequestParameterException( InvalidRequestParameterException.INVALID_REQUEST_PARAMETER, "Email is required" ); } } } ``` -------------------------------- ### Using Session Data in Controllers Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Example of injecting UserSession directly into a controller method to access authenticated user details. ```java @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/users") public class UserController { // UserSession is auto-resolved from token header (default: "Access-Token") // If token is missing → 401 (NOT_FOUND_ACCESS_TOKEN) // If token is invalid → 401 (INVALID_ACCESS_TOKEN) // If token is expired → 401 (EXPIRE_ACCESS_TOKEN) @GetMapping("/me") public UserProfile getMyProfile(UserSession session) { return userService.getProfile(session.getUserId()); } } ``` -------------------------------- ### User Creation Endpoint with Validation Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Example of a Spring Boot controller endpoint for creating a user, utilizing `@Valid` for request body validation. ```java @PostMapping("/users") public User createUser(@Valid @RequestBody UserCreateRequest req) { return userService.create(req); } ``` -------------------------------- ### Delete Operations Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Provides examples of various delete operations including deleteById, delete, remove, and conditional delete using deleteWhere. ```java userRepository.deleteById(1L); userRepository.delete(1L); // alias for deleteById userRepository.remove(1L); // alias for deleteById // Conditional delete userRepository.deleteWhere(Map.of("status", "INACTIVE")); ``` -------------------------------- ### Enum Definition with Javadoc Comments Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Shows an example of an enum definition where each enum constant is accompanied by a Javadoc comment explaining its meaning and state. ```java public enum OrderStatus { /** 결제 대기 — 주문이 생성되었으나 결제가 완료되지 않은 상태 */ PENDING, /** 결제 완료 — 결제가 확인되어 상품 준비 중인 상태 */ PAID, /** 배송 중 — 택배사에 인계되어 배송이 진행 중인 상태 */ SHIPPED, /** 배송 완료 — 수령인이 상품을 수령한 상태 */ DELIVERED, /** 주문 취소 — 고객 요청 또는 시스템에 의해 취소된 상태. 환불 처리 필요 */ CANCELLED } ``` -------------------------------- ### Custom Mapper with Pagination - LIKE Search Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Example of a custom mapper method for searching orders with LIKE, utilizing pagination. The framework's interceptor handles COUNT and LIMIT clauses. ```java @Mapper public interface OrderMapper { // LIKE search with pagination @Select("SELECT * FROM orders WHERE product_name LIKE CONCAT('%', #{keyword}, '%')") XPage searchOrders(XPagination pagination, @Param("keyword") String keyword); // BETWEEN range query @Select("SELECT * FROM orders WHERE created_at BETWEEN #{from} AND #{to}") XPage findByDateRange(XPagination pagination, @Param("from") LocalDateTime from, @Param("to") LocalDateTime to); // JOIN with pagination @Select("SELECT o.*, u.name AS user_name FROM orders o " + "INNER JOIN users u ON o.user_id = u.id " + "WHERE o.status = #{status}") XPage findOrdersWithUser(XPagination pagination, @Param("status") String status); // Complex: LIKE + JOIN + multiple conditions @Select("SELECT o.*, u.name AS user_name FROM orders o " + "INNER JOIN users u ON o.user_id = u.id " + "WHERE o.status = #{status} " + "AND o.product_name LIKE CONCAT('%', #{keyword}, '%')") XPage searchOrdersWithUser(XPagination pagination, @Param("status") String status, @Param("keyword") String keyword); // Dynamic SQL with ") XPage searchWithFilters(XPagination pagination, @Param("status") String status, @Param("keyword") String keyword); } ``` -------------------------------- ### Custom Session Data Definition Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Example of extending the SessionData class to define custom session attributes like userId, userName, and roles. ```java @Data public class UserSession extends SessionData { /** 사용자 고유 ID */ private Long userId; /** 사용자 이름 */ private String userName; /** 사용자 권한 목록 (예시: ["ADMIN", "USER"]) */ private List roles; } ``` -------------------------------- ### Controller with Auto-Resolved Session Data Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Example of a controller endpoint where session data is automatically resolved from the token header without explicit annotations. ```java @GetMapping("/me") public UserProfile getMyProfile(UserSession session) { return userService.getProfile(session.getUserId()); } ``` -------------------------------- ### Get User by ID Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Retrieves a specific user by their unique identifier. ```APIDOC ## GET /api/v1/users/{id} ### Description Retrieves a specific user by their unique identifier. ### Method GET ### Endpoint /api/v1/users/{id} ### Parameters #### Path Parameters - **id** (Long) - Required - The unique identifier of the user. ### Response #### Success Response (200) - **User** (User) - The user object matching the provided ID. #### Response Example { "example": "{\"id\": 1, \"name\": \"John Doe\", \"email\": \"john.doe@example.com\"}" } ``` -------------------------------- ### Custom Token Header Configuration Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Example of overriding getAccessTokenHeader() in a custom token handler to use a different HTTP header for token transmission. ```java @Component public class CustomTokenHandler extends XBaseAccessTokenHandler { @Override public String getAccessTokenHeader() { return "Authorization"; // Use standard Authorization header } } ``` -------------------------------- ### Programmatic XWebClient Creation via Factory Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Shows how to create and use XWebClient instances dynamically using XWebClientFactory. ```java @Service @RequiredArgsConstructor public class ExternalApiService { private final XWebClientFactory webClientFactory; public User getUser(Long id) { XWebClient client = webClientFactory.create("http://external-api.com"); return client.get("/users/{id}", User.class, id); } public User createUser(UserRequest request) { XWebClient client = webClientFactory.create("http://external-api.com"); return client.post("/users", request, User.class); } } ``` -------------------------------- ### Pagination with XPagination and XPage Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates how to use XPagination for defining sort orders and XPage for retrieving paginated results from repository methods. ```java // page=1, size=20 by default — ready to use immediately XPagination pagination = new XPagination(); pagination.addOrder(new XOrder("createdAt", XDirection.DESC)); pagination.addOrder(new XOrder("name", XDirection.ASC)); // Paginated findAll XPage result = userRepository.findAll(pagination); result.getTotalCount(); // total matching rows result.getPage(); // current page number result.getPageRows(); // rows for this page result.getHasNext(); // true if more pages exist // Paginated conditional query XPage filtered = userRepository.findWhere( pagination, Map.of("status", "ACTIVE") ); ``` -------------------------------- ### XWebClient - Programmatic Creation Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Creating XWebClient instances dynamically using XWebClientFactory. ```APIDOC ## GET /users/{id} ### Description Retrieves a user by their ID using a dynamically created XWebClient. ### Method GET ### Endpoint /users/{id} ### Parameters #### Path Parameters - **id** (Long) - Required - The ID of the user to retrieve. ### Response #### Success Response (200) - **User** (User) - The retrieved user details. ## POST /users ### Description Creates a new user using a dynamically created XWebClient. ### Method POST ### Endpoint /users ### Parameters #### Request Body - **request** (UserRequest) - Required - The request body containing user details. ### Response #### Success Response (200) - **User** (User) - The created user details. ``` -------------------------------- ### Entity Class Mapping with Annotations Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates how to map a Java class to a database table and its fields to columns using Axim annotations. Includes primary key, auto-increment, and default value configurations. ```java @Data @XEntity("orders") public class Order { /** 주문 고유 식별자 (Auto Increment) */ @XColumn(isPrimaryKey = true, isAutoIncrement = true) private Long id; /** * 주문 번호 * - 형식: "ORD-{yyyyMMdd}-{6자리 시퀀스}" * - 예시: "ORD-20240115-000001" * - UNIQUE 제약조건 적용 */ private String orderNo; /** * 주문 상태 * - "PENDING": 결제 대기 * - "PAID": 결제 완료 * - "SHIPPED": 배송 중 * - "DELIVERED": 배송 완료 * - "CANCELLED": 주문 취소 * @see OrderStatus */ private String status; /** * 주문 총 금액 (단위: 원, KRW) * - 소수점 2자리까지 허용 * - 음수 불가 * - 예시: 15000.00 */ private BigDecimal totalAmount; /** * 주문자 ID (users 테이블 FK) * - NULL 불가 */ private Long userId; /** 주문 생성 일시 (INSERT 시 자동 설정) */ @XDefaultValue(value = "NOW()", isDBValue = true) private LocalDateTime createdAt; /** 주문 수정 일시 (UPDATE 시 자동 갱신) */ @XDefaultValue(updateValue = "NOW()", isDBValue = true) private LocalDateTime updatedAt; } ``` -------------------------------- ### Create User Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Creates a new user resource. ```APIDOC ## POST /api/v1/users ### Description Creates a new user resource. ### Method POST ### Endpoint /api/v1/users ### Request Body - **user** (User) - Required - The user object to be created. ### Request Example { "example": "{\"name\": \"Jane Doe\", \"email\": \"jane.doe@example.com\"}" } ### Response #### Success Response (200) - **User** (User) - The newly created user object. #### Response Example { "example": "{\"id\": 2, \"name\": \"Jane Doe\", \"email\": \"jane.doe@example.com\"}" } ``` -------------------------------- ### Use Framework Pagination Classes Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Always use the framework's XPagination and XPage classes for pagination. Do not create custom pagination classes. ```java // ✗ WRONG — creating custom pagination public class PageRequest { int page; int size; } public class PageResponse { List items; long total; } ``` ```java // ✓ CORRECT — use framework classes XPagination pagination = new XPagination(); XPage result = userRepository.findAll(pagination); ``` -------------------------------- ### Controller with Offset-Based Pagination Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Shows how to implement offset-based pagination using XPaginationDefault, suitable for scrollable interfaces. ```java @GetMapping("/scroll") public XPage scrollUsers( @XPaginationDefault(offset = 0, size = 20) XPagination pagination) { return userRepository.findAll(pagination); } ``` -------------------------------- ### Controller Pattern for Pagination Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Illustrates the controller pattern for handling pagination requests using `@XPaginationDefault`. It ensures consistent pagination parameter binding. ```java @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/orders") public class OrderController { private final OrderService orderService; // Pagination: ?page=1&size=20&sort=createdAt,desc @GetMapping public XPage searchOrders(@XPaginationDefault XPagination pagination, @RequestParam(required = false) String keyword) { return orderService.search(pagination, keyword); } } ``` -------------------------------- ### saveAll() - Batch INSERT Operation Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Shows how to use saveAll() for performing batch INSERT operations, using INSERT IGNORE for efficiency. ```java List users = List.of(user1, user2, user3); userRepository.saveAll(users); // INSERT IGNORE INTO users ... VALUES (...), (...), (...) ``` -------------------------------- ### Default Pagination Parameters Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Illustrates the default values for page and size in XPagination. ```java public static final int DEFAULT_PAGE = 1; public static final int DEFAULT_SIZE = 20; ``` -------------------------------- ### Injecting and Using REST Clients Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates how to inject and use a REST client proxy within a Spring service. ```java @Service @RequiredArgsConstructor public class OrderService { private final OrderServiceClient orderClient; // Auto-injected proxy public Order getOrder(Long id) { return orderClient.getOrder(id); } public XPage listOrders(XPagination pagination) { // XPagination is automatically converted to query params return orderClient.listOrders(pagination); } } ``` -------------------------------- ### Generating Access Token on Login Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Controller code demonstrating how to authenticate a user and generate an access token using XAccessTokenParseHandler. ```java @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/auth") public class AuthController { private final XAccessTokenParseHandler tokenHandler; @PostMapping("/login") public Map login(@RequestBody LoginRequest request) { // Authenticate user... UserSession session = new UserSession(); session.setSessionId(UUID.randomUUID().toString()); session.setUserId(authenticatedUser.getId()); session.setUserName(authenticatedUser.getName()); session.setRoles(authenticatedUser.getRoles()); String token = tokenHandler.generateAccessToken(session); return Map.of("accessToken", token); } } ``` -------------------------------- ### User Service Implementation Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Handles user-related business logic, including creation, upserting, partial updates, and retrieving users based on status or paginated lists. ```java @Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; public User create(User user) { userRepository.save(user); return user; } public User upsert(User user) { user.setId(1L); userRepository.save(user); // atomic upsert return user; } public User partialUpdate(Long id, UserUpdateRequest req) { User user = new User(); user.setId(id); user.setName(req.getName()); // only set fields to update userRepository.modify(user); // selective UPDATE return userRepository.findOne(id); } public List findActive() { return userRepository.findWhere(Map.of("status", "ACTIVE")); } public XPage list(int page, int size) { XPagination pagination = new XPagination(); pagination.setPage(page); pagination.setSize(size); pagination.addOrder(new XOrder("createdAt", XDirection.DESC)); return userRepository.findAll(pagination); } } ``` -------------------------------- ### Defining Custom Exception with ErrorCode Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Illustrates how to create custom exception classes by extending `XRestException` and defining new `ErrorCode` constants for specific application errors. ```java public class UserException extends XRestException { public static final ErrorCode DUPLICATE_EMAIL = new ErrorCode("2001", "user.error.duplicate-email"); public static final ErrorCode INACTIVE_ACCOUNT = new ErrorCode("2002", "user.error.inactive-account"); public UserException(ErrorCode error) { super(HttpStatus.BAD_REQUEST, error); } public UserException(ErrorCode error, String description) { super(HttpStatus.BAD_REQUEST, error, description); } } // Usage throw new UserException(UserException.DUPLICATE_EMAIL, "alice@example.com already exists"); ``` ```properties # messages.properties (English) user.error.duplicate-email=Email already exists. user.error.inactive-account=Account is inactive. # messages_ko.properties (Korean) user.error.duplicate-email=이미 존재하는 이메일입니다. user.error.inactive-account=비활성화된 계정입니다. ``` -------------------------------- ### Custom Token Handler Implementation Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Shows how to create a custom token handler by extending XBaseAccessTokenHandler to customize token generation or parsing logic. ```java @Component public class CustomTokenHandler extends XBaseAccessTokenHandler { // Customize token generation/parsing as needed } ``` -------------------------------- ### Controller with Custom Default Pagination Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates setting custom default pagination parameters for a specific endpoint, including page size and sort order. ```java @GetMapping("/recent") public XPage recentUsers( @XPaginationDefault(page = 1, size = 50, column = "createdAt", direction = XDirection.DESC) XPagination pagination) { return userRepository.findAll(pagination); } ``` -------------------------------- ### XWebClient Simple API Calls Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates basic CRUD operations using XWebClient with Class for response types. ```java client.get("/users/{id}", User.class, id); client.post("/users", body, User.class); client.put("/users/{id}", body, User.class, id); client.patch("/users/{id}", body, User.class, id); client.delete("/users/{id}", Void.class, id); ``` -------------------------------- ### Session and Token Configuration Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Configure session and token settings, including the HMAC secret key for signing tokens and token expiration duration. ```properties axim.rest.session.secret-key=your-hmac-secret-key axim.rest.session.token-expire-days=90 ``` -------------------------------- ### Find Operations Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Illustrates various find operations including finding by primary key, finding all, existence checks, counting, and conditional queries using findWhere, findOneWhere, and count. ```java User user = userRepository.findOne(1L); // by primary key List all = userRepository.findAll(); // all rows boolean exists = userRepository.exists(1L); // existence check long total = userRepository.count(); // total count // Conditional queries List active = userRepository.findWhere(Map.of("status", "ACTIVE")); User single = userRepository.findOneWhere(Map.of("email", "alice@example.com")); long activeCount = userRepository.count(Map.of("status", "ACTIVE")); ``` -------------------------------- ### XWebClient Builder API for Complex Requests Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Illustrates using the XWebClient builder for requests with custom headers, query parameters, and request bodies. ```java client.spec() .get("/users?keyword=" + keyword) .header("X-API-Key", "my-key") .header("Authorization", "Bearer " + token) .body(requestBody) .retrieve(new ParameterizedTypeReference>() {}); ``` -------------------------------- ### Registering XWebClient Beans via Properties Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Configures named XWebClient beans using application properties for declarative registration. ```properties # Each entry creates a named XWebClient bean (lazy-initialized, cached) axim.web-client.services.userClient=http://user-service:8080 axim.web-client.services.orderClient=http://order-service:8080 axim.web-client.services.paymentClient=http://payment-service:8080 ``` -------------------------------- ### XWebClient - Declarative Bean Registration Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Registering XWebClient beans via properties and injecting them using @Qualifier. ```APIDOC ## GET /users/{id} ### Description Retrieves a user by their ID using a pre-configured XWebClient bean. ### Method GET ### Endpoint /users/{id} ### Parameters #### Path Parameters - **id** (Long) - Required - The ID of the user to retrieve. ### Response #### Success Response (200) - **User** (User) - The retrieved user details. ## GET /orders/{id} ### Description Retrieves an order by its ID using a pre-configured XWebClient bean. ### Method GET ### Endpoint /orders/{id} ### Parameters #### Path Parameters - **id** (Long) - Required - The ID of the order to retrieve. ### Response #### Success Response (200) - **Order** (Order) - The retrieved order details. ``` -------------------------------- ### insert() - Plain INSERT Operation Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates the insert() method for performing a plain INSERT operation, always setting the auto-generated ID on the entity. ```java User user = new User(); user.setName("Bob"); userRepository.insert(user); // Always performs INSERT, auto-generated ID set on entity ``` -------------------------------- ### Controller with Optional Session Data Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates handling an optional session parameter in a controller, useful when the session might not be present. ```java @GetMapping("/public") public Content getContent(@Nullable UserSession session) { if (session != null) { return contentService.getPersonalized(session.getUserId()); } return contentService.getDefault(); } ``` -------------------------------- ### XWebClient Generic Type Handling Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Shows how to handle generic response types like List using ParameterizedTypeReference. ```java client.get("/users", new ParameterizedTypeReference>() {}); ``` -------------------------------- ### Declarative REST Client - Direct Mode Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Configures a REST client to connect directly to a service host. The URL is constructed using the specified host and the API path. ```java @XRestService(value = "user-service", host = "${USER_SERVICE_HOST:http://localhost:8081}") public interface UserServiceClient { @XRestAPI(value = "/users/{id}", method = XHttpMethod.GET) User getUser(@PathVariable("id") Long id); } ``` -------------------------------- ### Injecting XWebClient Beans with @Qualifier Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates injecting specific XWebClient beans into a service using the @Qualifier annotation. ```java @Service @RequiredArgsConstructor public class ExternalApiService { @Qualifier("userClient") private final XWebClient userClient; @Qualifier("orderClient") private final XWebClient orderClient; public User getUser(Long id) { return userClient.get("/users/{id}", User.class, id); } public Order getOrder(Long id) { return orderClient.get("/orders/{id}", Order.class, id); } } ``` -------------------------------- ### Application Properties for Axim REST Framework Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Configure essential properties for your application, including database connection details, MyBatis settings, and optional Axim framework configurations for HTTP client and gateway routing. ```properties # ── DataSource ── spring.datasource.url=jdbc:mysql://localhost:3306/mydb spring.datasource.username=root spring.datasource.password= spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # ── MyBatis ── mybatis.config-location=classpath:mybatis-config.xml # ── Framework: HTTP Client (optional) ── axim.rest.client.pool-size=200 # Max HTTP connection pool size (default: 200) axim.rest.client.connection-request-timeout=30 # Connection request timeout in seconds (default: 30) axim.rest.client.response-timeout=30 # Response timeout in seconds (default: 30) axim.rest.debug=false # Enable REST client request/response logging (default: false) # ── Framework: Gateway Routing (optional) ── axim.rest.gateway.host=http://api-gateway:8080 # Gateway base URL (enables gateway mode for @XRestService) ``` -------------------------------- ### Argument Resolvers for Controllers Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Explains that the framework automatically registers XPagination and SessionData argument resolvers for controller methods via XWebMvcConfiguration. ```java // The framework registers two argument resolvers via XWebMvcConfiguration. // These automatically inject XPagination and SessionData subclasses into controller method parameters. ``` -------------------------------- ### save() - Upsert Operation Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Illustrates the save() method for both INSERT (when PK is null) and atomic upsert (when PK is present) operations. ```java // When PK is null: plain INSERT (auto-generated ID is set on entity) User user = new User(); user.setName("Alice"); userRepository.save(user); // user.getId() now contains the generated ID // When PK is present: INSERT ... ON DUPLICATE KEY UPDATE (atomic upsert) user.setId(1L); userRepository.save(user); ``` -------------------------------- ### Error Handling in REST Client Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Shows how to catch and handle exceptions thrown by the REST client, accessing original HTTP status and error details. ```java try { Order order = orderClient.getOrder(id); } catch (XRestException e) { e.getStatus(); // Original HTTP status (400, 404, 500, etc.) e.getCode(); // Error code string from ApiError e.getMessage(); // Error message e.getDescription(); // Additional description } ``` -------------------------------- ### Order Item Repository with Composite Key Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Defines an OrderItemRepository for entities with composite keys, demonstrating lookup, delete, and save operations using the composite key. ```java @XRepository public interface OrderItemRepository extends IXRepository {} // Lookup / delete by composite key OrderItemKey key = new OrderItemKey(); key.setOrderId(1L); key.setItemId(100L); OrderItem item = repository.findOne(key); // WHERE order_id = ? AND item_id = ? repository.delete(key); // WHERE order_id = ? AND item_id = ? // save() — all PKs set → upsert, any PK null → insert repository.save(orderItem); // insert() — returns OrderItemKey with both PK values OrderItemKey savedKey = repository.insert(orderItem); ``` -------------------------------- ### Default Value Handling Patterns Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates four patterns for handling default values using @XDefaultValue annotation: DB DEFAULT, literal string, DB expression, and auto-set on update. ```java // Pattern 1: Use DB DEFAULT (column omitted from INSERT) @XDefaultValue(isDBDefaultUsed = true) private String region; // Pattern 2: Literal string value on INSERT @XDefaultValue(value = "ACTIVE", isDBDefaultUsed = false) private String status; // INSERT: VALUES(..., 'ACTIVE', ...) // Pattern 3: DB expression on INSERT @XDefaultValue(value = "NOW()", isDBValue = true, isDBDefaultUsed = false) private LocalDateTime createdAt; // INSERT: VALUES(..., NOW(), ...) // Pattern 4: Auto-set value on UPDATE @XDefaultValue(updateValue = "NOW()", isDBValue = true) private LocalDateTime updatedAt; // UPDATE: SET updated_at = NOW() ``` -------------------------------- ### Gradle Dependencies for Axim REST Framework Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Add these dependencies to your Gradle build file to include the Axim REST Framework core, REST API, and MyBatis modules. ```gradle repositories { mavenCentral() maven { url 'https://jitpack.io' } } dependencies { implementation 'com.github.Axim-one.rest-framework:core:1.3.1' implementation 'com.github.Axim-one.rest-framework:rest-api:1.3.1' implementation 'com.github.Axim-one.rest-framework:mybatis:1.3.1' } ``` -------------------------------- ### Service Layer Calling Paginated Mapper Methods Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates how a service layer calls custom paginated mapper methods. It shows conditional logic for choosing between repository and mapper calls. ```java @Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; // simple CRUD private final OrderMapper orderMapper; // complex queries public XPage search(XPagination pagination, String keyword) { if (keyword != null) { return orderMapper.searchOrders(pagination, keyword); } return orderRepository.findAll(pagination); } public XPage searchWithUser(XPagination pagination, String status, String keyword) { return orderMapper.searchOrdersWithUser(pagination, status, keyword); } } ``` -------------------------------- ### Basic Custom Mapper (No Pagination) Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Defines a Java mapper interface for basic data retrieval without pagination. Use for simple SELECT statements. ```java @Mapper public interface UserMapper { @Select("SELECT * FROM users WHERE email LIKE CONCAT('%', #{keyword}, '%')") List searchByEmail(@Param("keyword") String keyword); @Select("SELECT u.*, o.order_count FROM users u " + "LEFT JOIN (SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id) o " + "ON u.id = o.user_id WHERE u.status = #{status}") List> findUsersWithOrderCount(@Param("status") String status); } ``` -------------------------------- ### User Repository Definition Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Defines a UserRepository interface extending IXRepository with @XRepository annotation, including custom query derivation methods. ```java @XRepository public interface UserRepository extends IXRepository { // All IXRepository methods are available automatically. // Add query derivation methods below: User findByEmail(String email); List findByStatus(String status); boolean existsByEmail(String email); long countByStatus(String status); int deleteByStatusAndName(String status, String name); } ``` -------------------------------- ### MyBatis Configuration Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Essential MyBatis configuration for the framework, including settings, custom object factory, interceptors, and mappers. ```xml ``` -------------------------------- ### Session Configuration Properties Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Configuration properties for session management, including the secret key for signing tokens and token expiration duration. ```properties # HMAC-SHA256 signing key — MUST be set in production axim.rest.session.secret-key=your-secret-key # Token expiration in days (default: 90) axim.rest.session.token-expire-days=90 ``` -------------------------------- ### Order Repository Interface Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Defines methods for interacting with the Order data, supporting single and multiple conditions, counting, existence checks, and deletion. ```java @XRepository public interface OrderRepository extends IXRepository { // Single condition Order findByOrderNo(String orderNo); // WHERE order_no = ? // Multiple conditions with And List findByUserIdAndStatus(Long userId, String status); // WHERE user_id = ? AND status = ? // Count with condition long countByStatus(String status); // SELECT COUNT(*) FROM orders WHERE status = ? // Existence check boolean existsByOrderNo(String orderNo); // SELECT COUNT(*) FROM orders WHERE order_no = ? > 0 // Delete with conditions int deleteByUserIdAndStatus(Long userId, String status); // DELETE FROM orders WHERE user_id = ? AND status = ? } ``` -------------------------------- ### DTO/Request Class with Validation Annotations Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Illustrates a Data Transfer Object (DTO) or Request class with validation annotations for fields like product IDs, shipping address, and payment method. Includes constraints such as 'not empty', 'not blank', and maximum length. ```java @Data public class OrderCreateRequest { /** * 주문할 상품 ID 목록 * - 최소 1개 이상 필수 * - 예시: [1, 2, 3] */ @NotEmpty private List productIds; /** * 배송지 주소 * - 전체 도로명 주소 (우편번호 제외) * - 예시: "서울특별시 강남구 테헤란로 123 4층" * - 최대 200자 */ @NotBlank @Size(max = 200) private String shippingAddress; /** * 배송 메모 (선택사항) * - 예시: "부재 시 경비실에 맡겨주세요" * - 최대 500자, NULL 허용 */ @Size(max = 500) private String deliveryNote; /** * 결제 수단 코드 * - "CARD": 신용/체크카드 * - "BANK": 무통장입금 * - "KAKAO": 카카오페이 * - "NAVER": 네이버페이 */ @NotBlank private String paymentMethod; } ``` -------------------------------- ### OrderServiceClient Interface Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Defines the interface for interacting with the order service using declarative annotations. ```APIDOC ## GET /orders/{id} ### Description Retrieves a specific order by its ID. ### Method GET ### Endpoint /orders/{id} ### Parameters #### Path Parameters - **id** (Long) - Required - The ID of the order to retrieve. ### Response #### Success Response (200) - **Order** (Order) - The retrieved order details. ## POST /orders ### Description Creates a new order. ### Method POST ### Endpoint /orders ### Parameters #### Request Body - **request** (OrderCreateRequest) - Required - The request body containing order details. ### Response #### Success Response (200) - **Order** (Order) - The created order details. ## GET /orders ### Description Searches for orders based on status and keyword. ### Method GET ### Endpoint /orders ### Parameters #### Query Parameters - **status** (String) - Required - The status to filter orders by. - **keyword** (String) - Required - The keyword to search within orders. ### Response #### Success Response (200) - **List** (List) - A list of orders matching the search criteria. ## GET /orders ### Description Retrieves a list of orders, potentially filtered by tenant ID. ### Method GET ### Endpoint /orders ### Parameters #### Request Header - **X-Tenant-Id** (String) - Required - The tenant ID for filtering. ### Response #### Success Response (200) - **List** (List) - A list of orders for the specified tenant. ## GET /orders ### Description Lists orders with pagination support. ### Method GET ### Endpoint /orders ### Parameters #### Query Parameters - **page** (Integer) - Optional - The page number for pagination. - **size** (Integer) - Optional - The number of items per page. - **sort** (String) - Optional - The sorting criteria (e.g., 'createdAt,DESC'). ### Response #### Success Response (200) - **XPage** (XPage) - A paginated list of orders. ## PUT /orders/{id} ### Description Updates an existing order. ### Method PUT ### Endpoint /orders/{id} ### Parameters #### Path Parameters - **id** (Long) - Required - The ID of the order to update. #### Request Body - **request** (OrderUpdateRequest) - Required - The request body containing updated order details. #### Request Header - **Access-Token** (String) - Required - The access token for authentication. ### Response #### Success Response (200) - **Order** (Order) - The updated order details. ``` -------------------------------- ### Maven Dependencies for Axim REST Framework Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Include these dependencies in your Maven project's pom.xml to integrate the Axim REST Framework. ```xml jitpack.io https://jitpack.io com.github.Axim-one.rest-framework core 1.3.1 com.github.Axim-one.rest-framework rest-api 1.3.1 com.github.Axim-one.rest-framework mybatis 1.3.1 ``` -------------------------------- ### Custom Mapper SQL Pitfalls Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Avoid adding `ORDER BY` or `LIMIT` clauses in custom mapper SQL when using `XResultInterceptor`, as it handles pagination automatically. ```java // ✗ WRONG — ORDER BY and LIMIT conflict with interceptor @Select("SELECT * FROM users WHERE status = #{status} ORDER BY created_at DESC LIMIT 20") XPage findByStatus(XPagination pagination, @Param("status") String status); // ✓ CORRECT — only write the base SELECT @Select("SELECT * FROM users WHERE status = #{status}") XPage findByStatus(XPagination pagination, @Param("status") String status); ``` -------------------------------- ### REST Client JSON Serialization Date Format Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Illustrates the date serialization format used by the framework's ObjectMapper. ```java // Dates are serialized/deserialized as "yyyy-MM-dd HH:mm:ss" // ✓ "2024-01-15 14:30:00" // ✗ "2024-01-15T14:30:00Z" (ISO 8601 — not used) ``` -------------------------------- ### Using Field Names vs. Column Names Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Query derivation and `findWhere()` methods use Java field names (camelCase), not database column names (snake_case). Ensure consistency to avoid errors. ```java // ✗ WRONG — using snake_case column name User findByUser_name(String name); userRepository.findWhere(Map.of("user_name", "Alice")); // ✓ CORRECT — using camelCase field name User findByUserName(String name); userRepository.findWhere(Map.of("userName", "Alice")); ``` -------------------------------- ### Application and Framework Messages Properties Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Defines application-specific and framework default messages for internationalization. Application messages override framework messages with the same key. ```properties # messages.properties (English — application custom messages) user.error.duplicate-email=Email already exists. user.error.inactive-account=Account is inactive. # messages_ko.properties (Korean) user.error.duplicate-email=이미 존재하는 이메일입니다. user.error.inactive-account=비활성화된 계정입니다. ``` ```properties server.http.error.invalid-parameter=Invalid request parameter. server.http.error.not-support-method=HTTP method not supported. server.http.error.required-auth=Authentication required. server.http.error.invalid-auth=Invalid authentication credentials. server.http.error.expire-auth=Authentication expired. server.http.error.notfound-api=API not found. server.http.error.server-error=Internal server error. server.http.error.unknown-server-error=Unknown server error. server.http.error.no-response-server=No response from server. ``` -------------------------------- ### i18n Message Resolution in Java Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Demonstrates how to resolve internationalized messages using Spring's MessageSource. The third argument acts as a default message if the key is not found. ```java messageSource.getMessage(code, args, code, locale) ``` -------------------------------- ### Internationalization (i18n) Messages Configuration Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Configure internationalization settings, such as the default language and the HTTP header used for language detection. ```properties axim.rest.message.default-language=ko-KR axim.rest.message.language-header=Accept-Language spring.messages.basename=messages spring.messages.encoding=UTF-8 ``` -------------------------------- ### Pass Non-Empty Map to findWhere() Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md The findWhere method requires a non-empty map. Use findAll() for unconditional queries. Passing an empty map will result in an IllegalArgumentException. ```java // ✗ WRONG — empty map throws IllegalArgumentException userRepository.findWhere(Map.of()); ``` ```java // ✓ CORRECT userRepository.findAll(); userRepository.findWhere(Map.of("status", "ACTIVE")); ``` -------------------------------- ### @XDefaultValue Pitfalls and Correct Usage Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Understand the default behavior of `isDBDefaultUsed` in @XDefaultValue. Set `isDBDefaultUsed = false` to use a literal value, or `isDBValue = true` for database expressions. ```java // ✗ WRONG — isDBDefaultUsed defaults to true, so "ACTIVE" is IGNORED // The column is omitted from INSERT, DB DEFAULT is used instead @XDefaultValue(value = "ACTIVE") private String status; // ✓ CORRECT — explicitly set isDBDefaultUsed = false to use literal value @XDefaultValue(value = "ACTIVE", isDBDefaultUsed = false) private String status; // ✓ CORRECT — DB expression (isDBValue=true implies isDBDefaultUsed=false behavior) @XDefaultValue(value = "NOW()", isDBValue = true) private LocalDateTime createdAt; // ✓ CORRECT — intentionally use DB DEFAULT (column omitted from INSERT) @XDefaultValue(isDBDefaultUsed = true) private String region; // ✓ CORRECT — updateValue only (INSERT unaffected, UPDATE auto-sets) @XDefaultValue(updateValue = "NOW()", isDBValue = true) private LocalDateTime updatedAt; ``` -------------------------------- ### Query Derivation Method Name Parsing Rules Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Method names are split by 'And' only when preceded by lowercase and followed by uppercase. Field names with 'And' in them are not split. The parameter count must exactly match the number of parsed fields. ```java // "BrandName" → single field "brandName" (no split — "dN" is lowercase+uppercase within the field) User findByBrandName(String brandName); ``` ```java // "Status" + "Name" → two fields split by "And" List findByStatusAndName(String status, String name); ``` ```java // Parameter count MUST exactly match the number of parsed fields // ✗ WRONG — 2 fields parsed but 1 parameter List findByStatusAndName(String status); ``` -------------------------------- ### REST Framework Properties for i18n Source: https://github.com/axim-one/rest-framework/blob/main/docs/guide.md Configuration properties for setting the default language and the header used for language negotiation in the REST framework. ```properties # application.properties axim.rest.message.default-language=ko-KR axim.rest.message.language-header=Accept-Language ```